交待一下
本文先用一个简单的java文件生成了exe,再搭建了maven项目,又把maven项目生成了EXE,整个没有什么毛病。。。就是坑有点多
graalvm组件介绍
Graal编译器
GraalVM的核心就是Graal编译器,一款优秀的JIT编译器。它即可以当作JIT编译器使用,也可以用作提前编译的静态编译器。
Graal也是使用Java语言来编写(JVMCI),常见的编译器优化它都支持,并且支持更复杂的优化(部分逃逸分析、激进预测优化等),自JDK10开始,HotSpot也引入了Graal。主要作为C2的替代者,相较于C2,它借鉴了C2的优点,摈弃了C2的缺陷,在可维护性和可扩展性上面都优于C2(C2已经复杂到连原作者Cliff都不愿意再维护了)。但是Graal目前还处于实验阶段,默认不开启,需要使用参数来激活。
Truffle
truffle是GraalVM的另一个关键组件,它是一款编程语言的实现框架,它提供了一套API,可以用它来实现某一门语言的AST解释器,并可以被Graal编译器优化。目前大部分语言的解释器都已经由Oracle实现了,从上图可以看到:JS、Ruby、R、Python、Sulong(Sulong是一个针对LLVM IR的解释器)。也有很多人实现了其它语言的解释器并放在了Github上。
Truffle的精华在于:所有语言都是使用统一的协议来开发对应的解释器,也就意味着,运行时所有解释器都可以通过统一的协议来操作不同编程语言中的对象。所有不语言编写或混合编写的代码,在运行时都是一样的。都可以像优化正常代码一样,来优化不同语言或多语言写的程序,不会有任何额外的开销。
这意味着当你使用Java开发时,发现Python有一个非常好用的库,但是Java没有。这时你不用再使用Java再实现一遍Pyhton库,直接在Java代码中调用Python库即可。
另一个好处是,因为运行时看来所有的语言都是一样的,这就意味着工具也可以是多语言的,比如使用VisualVM来分析JavaScript的内存使用等
Substrate VM
源码 https://github.com/oracle/graal/tree/master/substratevm
SubstrateVM是GraalVM的一个AOT(Ahead-Of-Time)编译框架。AOT即提前编译,和即时编译(JIT)不同,AOT是在程序运行之前,就将字节码转换为机器码,可以直接编译成可独立运行的执行文件(本地镜像)或共享库。
SubstrateVM 的设计初衷是提供一个高启动性能、低内存开销,并且能够无缝衔接 C 代码的 Java 运行时。SubtrateVM完全脱离了HotSpot虚拟机,并拥有独立的运行时,包含异常处理,同步,线程管理,内存管理(垃圾回收)和 JNI 等组件。
从执行时间上来划分,SubstrateVM可以分为两部分:native image generator 以及 SubstrateVM 运行时:
n### ative image generator
包含了AOT 编译逻辑。它本身是一个 Java 程序,将使用 Graal 编译器将 Java 类文件编译为可执行文件或者动态链接库。
在进行编译之前,native image generator 将采用指针分析(points-to analysis),从用户提供的程序入口出发,探索所有可达的代码。在探索的同时,它还将执行初始化代码,并在最终生成可执行文件时,将已初始化的堆保存至一个堆快照之中。这样一来,SubstrateVM 将直接从目标程序开始运行,而无须重复进行 Java 虚拟机的初始化。
SubstrateVM 运行时
一个脱离了HotSpot的精简运行时,经过 AOT 编译的目标程序将跑在该运行时之上。
SubstrateVM的启动时间和内存开销非常少,而且由于是提前编译,则程序一启动便可以获得良好的性能(性能峰值可能会比JIT更差,但是稳定性会更好)。
但是它目前来看也有很多问题:
很明显它没有办法再做到 “Write once,Run anywhere” 了
Java反射机制是在运行期间动态的调用API,但是具体调用哪些接口,很明显在程序没有运行起来时的编译期是没办法知道的。需要开发者明确的告知GaalVM有哪些代码可能被反射调用(通过JSON配置文件的形式),这就很恶心了,基本上是不可实现的。
本地镜像由于没有运行在HotSpot上,那么一切HotSpot虚拟机本身的内部接口(JVMTI、JVMCI等)都不复存在。大量的Agent的调试工具都无法使用,噩梦!
在我们现在的微服务架构中,本地镜像要比完整的JVM环境+应用要好用的多,性能稳定、启动时间极快、消耗的资源也更加的少。但是前提是要能够解决上面的拦路虎。
gu(Graal updater)可以用来安装 Python,R以及 Ruby的语言包
graalvm相关下载
https://github.com/graalvm/graalvm-ce-builds/releases/tag/vm-20.2.0
- graalvm-ce-java17-windows-amd64-22.0.0.2
- native-image-installable-svm-java17-windows-amd64-22.0.0.2.jar
我是下载了这两个
关于C++编译器的环境变量
这个步骤必须成功,否则编译会失败。。
不知道gralvm是否支持clang编译,看网上大部分的文章都是用MSVC的编译器,都讲的是在环境变量中设置MSVC的地址,由于我之前编译过V8引擎,所以本地的C++环境有点乱,反正我是没设置成功。
我是直接装了VS2019。。至于那个版本我回头发上来
至于装VS的那些组件的可以看这个文章
https://blog.csdn.net/qq_26212181/article/details/121418452
由于装了VS2019之后 WIN的菜单栏会有一个专门启动CL编译器的CMD工具
注意看,我用的是X64的 不是X64-X86的!
在这个地方找出来
也可以直接到VS目录下找,注意 不同的VS版本可能路径不同,大家可以搜索一下
配置graalVm环境变量
GRAALVM_HOME
JAVA_HOME和graalvmHOME一样 环境变量都一样
D:\AAAA_WORK\java\graalvm-ce-java17-windows-amd64-22.0.0.2\graalvm-ce-java17-22.0.0.2
## 安装依赖
执行下面的命令`gu.cmd` 这个玩意儿 网上的屌丝们 都是说gu 实际上要gu.cmd 谢特
gu.cmd -L install native-image-installable-svm-java17-windows-amd64-22.0.0.2.jar
Processing Component archive: native-image-installable-svm-java17-windows-amd64-22.0.0.2.jar
Installing new component: Native Image (org.graalvm.native-image, version 22.0.0.2)
创建编译测试代码HelloWorld.java:
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello World");
}
}
编译HelloWorld为class
javac HelloWorld.java
进入到VS2019的专用编译工具(CMD)
输入 native-image HelloWorld
开始等待吧....
花了4分钟。。。。
执行生成的EXE
问题
- Get-Unique : 找不到接受实际参数“install ”的位置形式参数。
执行 gu 依赖管理时 出现的问题
大部分网上的文章 直接用的gu 其实在win下需要输入gu.cmd 坑爹把
命令行需要输入的是 gu.cmd
查看当前装了那些依赖
gu.cmd list
maven项目
本来想着用springboot项目生成exe的,但估计配置还有很多,所以先用maven项目试试
idea 搭建maven项目
https://www.cnblogs.com/lhboke/p/12858844.html
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<groupId>com.langs</groupId>
<version>1.0-SNAPSHOT</version>
<modelVersion>4.0.0</modelVersion>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
</properties>
<artifactId>demo3</artifactId>
<build>
<finalName>native-image-js</finalName>
<plugins>
<plugin>
<groupId>org.graalvm.nativeimage</groupId>
<artifactId>native-image-maven-plugin</artifactId>
<version>20.3.0</version>
<configuration>
<!-- imageName用于设置生成的二进制文件名称 -->
<imageName>${project.artifactId}</imageName>
<!-- mainClass用于指定main方法类路径 -->
<mainClass>com.lang.Application</mainClass>
<!-- native image 编译参数文档:https://docs.oracle.com/en/graalvm/enterprise/20/docs/reference-manual/native-image/NativeImageMavenPlugin/ -->
<buildArgs>
--no-fallback
</buildArgs>
</configuration>
<executions>
<execution>
<goals>
<goal>native-image</goal>
</goals>
<phase>package</phase>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
注意maven 项目的目录
src/main/java 这是必须的约定
否则生成的jar中不会有class 运行jar时,也会报找不到main class
maven项目生成EXE
实际上就是调用的 这样的指令... graalvm团队的思路就是maven生成JAR 然后用下面的指令打包
native-image -cp native-image-js.jar Hello
我先按照maven的方式打包(注意 我所有的操作都在X64的VS的命令行工具下执行的)
mvn -Pnative clean package
生成后的EXE
出现问题
Image building on Java 11+ without native-image requires MAVEN_OPTS='--add-exports=java.base/jdk.internal.module=ALL-UNNAMED'
这个问题 总的说来是graalvm的BUG
把graalvm下的svm/bin下的native-image.exe 拷贝到 D:\AAAA_WORK\java\graalvm-ce-java17-windows-amd64-22.0.0.2\graalvm-ce-java17-22.0.0.2\bin 下面
对比下两个路径
D:\AAAA_WORK\java\graalvm-ce-java17-windows-amd64-22.0.0.2\graalvm-ce-java17-22.0.0.2\lib\svm\bin
D:\AAAA_WORK\java\graalvm-ce-java17-windows-amd64-22.0.0.2\graalvm-ce-java17-22.0.0.2\bin
这是graalvm对没有调起cmd那个文件引起的。
Error: Classes that should be initialized at run time got initialized during image building
出现这个情况 表示需要在编译参数中增加对应的类
class io.netty.util.internal.PlatformDependent0$6 cannot access class jdk.internal.misc.Unsafe (in module java.base) because module java.base does not export jdk.internal.misc to unnamed module
class io.netty.util.internal.PlatformDependent0$6 cannot access class jdk.internal.misc.Unsafe (in module java.base) because module java.base does not export jdk.internal.misc to unnamed module
这种情况是因为JAVA9以上的 三方包 需要在JVM编译参数中加上需要导入的module 上面就是缺少jdk.internal.misc.Unsafe
--add-exports=java.base/jdk.internal.misc.Unsafe=ALL-UNNAMED
参考
https://www.graalvm.org/docs/getting-started/
https://www.graalvm.org/reference-manual/native-image/
JAVAFX生成EXE 其中讲到了关于反射类和DLL的一些处理细节,值得一看
https://www.cnblogs.com/javalinux/p/15791024.html