jvm中gc算法(过两年 JVM 可能就要被 GraalVM 替代了)
大家好 ,我是风筝 ,公众号「古时的风筝」 ,专注于 Java技术 及周边生态 。
文章会收录在 JavaNewBee 中 ,更有 Java 后端知识图谱 ,从小白到大牛要走的路都在里面 。今天说一说 GraalVM。
GraalVM 是 Oracle 大力发展和想要推广的新一代 JVM ,目前很多框架都已经渐渐支持 GraalVM 了 ,比如我们在用的 Spring 也已经推出了对 GraalVM 兼容的工具包了 。
既然说的这么厉害 ,那么它到底是何方神圣呢 。
GraalVM 和 JVM 的关系
既然叫做VM ,那肯定和 JVM 有关系的吧 。JVM 全称 Java 虚拟机,我们都知道 ,Java 程序是运行在虚拟机上的 ,虚拟机提供 Java 运行时,支持解释执行和部分的(JIT)即时编译器 ,并且负责分配和管理 Java 运行所需的内存 ,我们所说的各种垃圾收集器都工作在 JVM 中 。
比如 Oracle JDK 、OpenJDK ,默认的 JVM 是 HotSpot 虚拟机 ,这是当前应用最广泛的一个虚拟机 。我们平时见到的各种将虚拟机的书籍 、文章、面试题 ,基本上都是说的 HotSpot 虚拟机 。
除此之外 ,还有一些商用 ,或者说小众的虚拟机存在 ,比如IBM 的J9 JVM ,商用的 Zing VM 等 。
那 GraalVM 是另一种 Java 虚拟机吗?
是 ,又不全是 。
GraalVM 可以完全取代上面提到的那几种虚拟机 ,比如 HotSpot 。把你之前运行在 HotSpot 上的代码直接平移到 GraalVM 上 ,不用做任何的改变,甚至都感知不到 ,项目可以完美的运行。
但是 GraalVM 还有更广泛的用途 ,不仅支持 Java 语言,还支持其他语言 。这些其他语言不仅包括嫡系的 JVM 系语言 ,例如 Kotlin 、Scala ,还包括例如 JavaScript 、Nodejs 、Ruby 、Python 等 。
GraalVM 的野心不止于此,看上面的图 ,它的目的是搭建一个 Framework ,最终的目标是想要支持任何一种语言 ,无论哪种语言 ,可以共同跑在 GraalVM 上 ,不存在跨语言调用的壁垒。
GraalVM 和JDK有什么关系
Java 虚拟机都是内置在 JDK 中的 ,比如Orcale JDK 、OpenJDK ,默认内置的都是 HotSpot 虚拟机 。
GraalVM 也是一种 JDK ,一种高性能的 JDK 。完全可以用它替代 OpenJDK 、Orcale JDK。
GraalVM 如何运行 Java 程序
说了半天 ,是不是还是不知道 GraalVM 到底是什么 。
GraalVM - 还包含 Graal (JIT)即时编译器,可以结合 HotSpot 使用
GraalVM – 是一种高性能 JDK ,旨在加速 Java 应用程序性能 ,同时消耗更少的资源 。
GraalVM - 是一种支持多语言混编的虚拟机程序,不仅可以运行 JVM 系列的语言 ,也可支持其他语言 。
GraalVM 提供了两种方式来运行 Java 程序 。
第一种:结合 HotSpot 使用
上面说了 ,GraalVM 包含 Graal (JIT)即时编译器,自从 JDK 9u 版本之后 ,Orcale JDK 和 OpenJDK 就集成了 Graal 即时编译器 。我们知道 Java 既有解释运行也有即时编译 。
当程序运行时 ,解释器首先发挥作用 ,代码可以直接执行 。随着时间推移 ,即时编译器逐渐发挥作用 ,把越来越多的代码编译优化成本地代码 ,来获取更高的执行效率 。即时编译器可以选择性地编译热点代码 ,省去了很多编译时间 ,也节省很多的空间 。比如多次执行的方法或者循环 、递归等。
JDK 默认使用的是 C2 即时编译器 ,C2是用C++编写的 。而使用下面的参数可以用 Graal 替换 C2 。
-XX:+UnlockExperimentalVMOptions -XX:+UseJVMCICompilerGraal 编译器是用 Java 实现的,用 Java 实现自己的编译器。Graal 基于一些假设的条件 ,采取更加激进的方式进行优化 。采用 Graal 编译器之后 ,对性能有会有一定的提升 。
但是如果你还是在用 JDK8,那对不起了 ,GraalVM 的一切都用不了。
第二种:AOT 编译本地可执行程序
这是 GraalVM 真正厉害的地方 。
AOT 提前编译 ,是相对于即时编译而言的 。AOT在运行过程中耗费 CPU 资源来进行即时编译,而程序也能够在启动的瞬间就达到理想的性能 。例如 C 和 C++语言采用的是AOT静态编译 ,直接将代码转换成机器码执行 。而 Java 一直采用的是解释 + 即时编译技术 ,大多数情况下 Java 即时编译的性能并不比静态编译差 ,但是还是一直朝着 AOT 编译的方向努力 。
但是 Java 对于 AOT 来说有一些难点 ,比如类的动态加载和反射调用 。
GraalVM 显然是已经克服了这些问题 ,使用 GraalVM 可以直接将 Java 代码编译成本地机器码形态的可执行程序 。
我们目前运行 Java 一定要安装 JDK 或者 JRE 对不对 ,如果将程序直接编译成可执行程序 ,就不用在服务器上安装 JDK 或 JRE 了 。那就是说运行 Java 代码其实也可以不用虚拟机了是吗?
GraalVM 的 AOT 编译实际上是借助了 SubstrateVM 编译框架 ,可以将 SubstrateVM 理解为一个内嵌精简版的 JVM ,包含异常处理,同步 ,线程管理 ,内存管理(垃圾回收)和 JNI 等组件 。
SubstrateVM 的启动时间非常短,内存开销非常少。用这种方式编译出的 Java 程序的执行时间可与C语言持平 。
下图是使用即时编译(JVM运行)与 AOT (原生可执行程序)两种方式的 CPU 和内存使用情况对比 ,可以看出来 ,AOT 方式下 CPU 和内存的使用都非常少 。
除了运行时占用的内存少之外,用这种方式最终生成的可执行文件也非常小。这对于云端部署非常友好 。目前很多场景下都使用 Docker 容器的方式部署 ,打一个 Java 程序的镜像包要包含完整的 JVM 环境和编译好的 Jar 包 。而AOT 方式可以最大限度的缩小 Docker 镜像的体积。
缺点
好处多多 ,当然也有一些弊端 。对于反射这种纯粹在运行时才能确定的部分 ,不可能完全通过优化编译器解决 ,只能通过增加配置的方式解决 。麻烦是麻烦了一点 ,但是是可行的 ,Spring Boot 2.7的版本已经支持原生镜像了 ,Spring 这种非常依赖反射的框架都可以支撑 ,我们用起来也应该没问题 。
GraalVM 如何支持多语言
要支持多语言 ,就要说到 GraalVM 中的另一个核心组件 Truffle 了 。
Truffle 是一个用 Java 写就的语言实现框架 。基于 Truffle 的语言实现仅需用 Java 实现词法分析 、语法分析以及针对语法分析所生成的抽象语法树(Abstract Syntax Tree,AST)的解释执行器 ,便可以享用由 Truffle 提供的各项运行时优化 。
就一个完整的 Truffle 语言实现而言 ,由于实现本身以及其所依赖的 Truffle 框架部分都是用 Java 实现的,因此它可以运行在任何 Java 虚拟机之上 。
当然 ,如果 Truffle 运行在附带了 Graal 编译器的 Java 虚拟机之上 ,那么它将调用 Graal 编译器所提供的 API,主动触发对 Truffle 语言的即时编译 ,将对 AST 的解释执行转换为执行即时编译后的机器码 。
目前除了 Java , JavaScript 、Ruby、Python 和许多其他流行语言都已经可以运行在 GraalVM 之上了 。
GraalVM 官方还提供了完整的文档 ,当有一天你开发了一款新的语言 ,也可以用 Truffle 让它跑在 GraalVM 上。
安装和使用
GraalVm 目前的最新版本是 22.3 ,分为社区版和企业版 ,就好像 OpenJDK 和 商用的 Orcale 的 JDK ,企业版会多一些性能分析的功能 ,用来帮助更大程度的优化性能 。
社区版是基于OpenJDK 11.0.17, 17.0.5, 19.0.1 ,而商业版基于Oracle JDK 8u351, 11.0.17, 17.0.5, 19.0.1,所以 ,如果你想用免费的 ,只能将程序升级到 JDK 11 以上了 。
GraalVM 支持 Windows 、Linux 、MacOS ,可以用命令安装最新版 ,或者直接下载对应 Java 版本的。
我是下载的 Java 11 的版本 ,下载下来的压缩包,直接解压 ,然后配置环境变量 。把解压目录配置到环境变量的 JAVA_HOME就可以了 。
解压好其实就相当于安装完毕了 ,查看一下版本。
进入到解压目录下的bin目录中 ,运行 java -version 。运行结果如下:
运行代码
常用方式运行
也就是我们平时一直在用的这种方式 ,把 GrralVM 当做 OpenJDK 使用 ,只不过把即时编译器换成了 Graal 。就是前面说的第一种方式 。
安装完成后 ,就可以把它当做正常的 JDK 使用了 ,比如 javac、jps 、jmap等都可以直接用了 。大多数人还是用 IDEA 的 ,所以就直接在 IDEA 中使用就好了 。
1 、先随意创建一个 Java 项目 。
2、创建完成后 ,打开项目设置 。
3 、在打开的项目设置弹出框中选择 SDKs,点击加号 ,选择前面解压的 GraalVM 目录 。
4 、然后选择刚刚添加的这个 JDK 。
5 、最后运行一段测试代码。
public class HelloWorld { public static void main(String[] args) throws Exception { System.out.println("Hello GraalVM!"); Thread.sleep(1000 * 100 * 100); } }上面这样的运行方式 ,其实就相当于前面说的第一种运行方式
native-image 方式运行
这种方式就是 AOT 编译成机器码,已可执行文件的形式出现 。native-image 可以命令行的形式执行 ,也可以在配合 Maven 执行 ,我这儿就直接演示用 Maven 形式的了,毕竟IDEA 搭配 Maven 用习惯了 。
1 、安装native-image 工具包
native-image 是用来进行 AOT 编译打包的工具 ,先把这个装上 ,才能进行后面的步骤。
安装好 GraalVM 后 ,在 bin目录下有一个叫做 gu的工具 ,用这个工具安装 ,如果将 bin目录添加到环境中 ,直接下面的命令安装就行了 。
gu install native-image如果没有将 bin目录加到环境变量中 ,要进入到 bin目录下 ,执行下面的命令安装 。
./gu install native-image这个过程可能比较慢 ,因为要去 github 上下载东西,如果一次没成功(比如超时) ,多试两次就好了。
2 、配置 Maven
配置各种版本
<properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>${java.specification.version} </maven.compiler.source> <maven.compiler.target>${java.specification.version}</maven.compiler.target> <native.maven.plugin.version>0.9.12</native.maven.plugin.version> <imageName>graalvm-demo-image</imageName> <mainClass>org.graalvm.HelloWorld</mainClass> </properties>native.maven.plugin.version是要用到的编译为可执行程序的 Maven 插件版本 。
imageName是生成的可执行程序的名称 。
mainClass是入口类全名称 。
配置 build 插件
<build> <plugins> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>exec-maven-plugin</artifactId> <version>3.0.0</version> <executions> <execution> <id>java-agent</id> <goals> <goal>exec</goal> </goals> <configuration> <executable>java</executable> <workingDirectory>${project.build.directory}</workingDirectory> <arguments> <argument>-classpath</argument> <classpath/> <argument>${mainClass}</argument> </arguments> </configuration> </execution> <execution> <id>native</id> <goals> <goal>exec</goal> </goals> <configuration> <executable>${project.build.directory}/${imageName}</executable> <workingDirectory>${project.build.directory}</workingDirectory> </configuration> </execution> </executions> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.1</version> <configuration> <source>${maven.compiler.source}</source> <target>${maven.compiler.source}</target> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <version>3.2.2</version> <configuration> <archive> <manifest> <addClasspath>true</addClasspath> <mainClass>${mainClass}</mainClass> </manifest> </archive> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-assembly-plugin</artifactId> <executions> <execution> <phase>package</phase> <goals> <goal>single</goal> </goals> </execution> </executions> <configuration> <archive> <manifest> <addClasspath>true</addClasspath> <mainClass>${mainClass}</mainClass> </manifest> </archive> <descriptorRefs> <descriptorRef>jar-with-dependencies</descriptorRef> </descriptorRefs> </configuration> </plugin> </plugins> </build>配置 profiles
<profiles> <profile> <id>native</id> <build> <plugins> <plugin> <groupId>org.graalvm.buildtools</groupId> <artifactId>native-maven-plugin</artifactId> <version>${native.maven.plugin.version}</version> <extensions>true</extensions> <executions> <execution> <id>build-native</id> <goals> <goal>build</goal> </goals> <phase>package</phase> </execution> <execution> <id>test-native</id> <goals> <goal>test</goal> </goals> <phase>test</phase> </execution> </executions> <configuration> <fallback>false</fallback> <buildArgs> <arg>-H:DashboardDump=fortune -H:+DashboardAll</arg> </buildArgs> <agent> <enabled>true</enabled> <options> <option>experimental-class-loader-support</option> </options> </agent> </configuration> </plugin> </plugins> </build> </profile> </profiles>3 、使用 maven 编译 ,打包成本地可执行程序 。
执行 Maven 命令
mvn clean package或者
mvn -Pnative -Dagent package编译打包的过程比较慢,因为要直接编译成机器码 ,所以比一般的编译过程要慢一些 。看到下面的输入日志 ,说明打包成功了 。
4 、运行可执行程序包,打开 target 目录 ,已经看到了graalvm-demo-image可执行程序包了 ,大小为 11.58M 。
然后就可以运行它了 ,进入到目录下 ,执行下面的命令运行 ,可以看到正常输出了 。注意了 ,这时候已经是没有用到本地 JVM 了 。
./graalvm-demo-image Hello GraalVM!这时候 ,用 jps -l命令已经看不到这个进程了 ,只能通过 ps看了。
总结
虽然我们还没有看到有哪个公司说在用 GraalVM 了 ,但是 Quarkus 、Spring Boot 、Spring等很多的框架都已经支持 GraalVM 的 Native-image 模式,而且在 Orcale 的大力推广下 ,相信不久之后就会出现在更多的产品中 。赶紧体验一下吧 。
如果觉得还不错的话 ,给个推荐吧!
公众号「古时的风筝」,Java 开发者 ,专注 Java 及周边生态。坚持原创干货输出 ,你可选择现在就关注我,或者看看历史文章再关注也不迟 。长按二维码关注 ,跟我一起变优秀!
创心域SEO版权声明:以上内容作者已申请原创保护,未经允许不得转载,侵权必究!授权事宜、对本内容有异议或投诉,敬请联系网站管理员,我们将尽快回复您,谢谢合作!