JVM 的未来——GraalVM 集成入门
要说JVM的未来那有很多的可能,但在云原生如日中天、Serverless日渐成熟、新语言百花齐放的当下,跨语言、Native支持、高性能低资源占用的技术必定是其璀璨的明珠,而GraalVM正是这样一个承载了JVM未来,将Java带入下一波技术浪潮的弄潮儿,本文我们就来实践下GraalVM集成支持。
Java的问题
在讲GraalVM前我们先回看下Java当前遇到的问题,概括而言如下:
云时代的掉队者,由于Java启动的高延时、对资源的高占用、导致在Serverless及FaaS架构下力不从心,在越来越流行的边缘计算、IoT方向上也是难觅踪影
系统级应用开发的旁观者,Java语言在业务服务开发中孤独求败,但在系统级应用领域几乎是C、C++、搅局者Go、黑天鹅Rust的天下
移动应用、敏捷应用的追随者,移动应用中Android逐步去Java,前端又是JS的世界,敏捷开发方面前有Ruby、Python后有NodeJS
有人说Java吃老本,不思进取,也对也不对吧,毕竟Java作为企业级软件开发最主流的语言,兼容稳定远胜于创新求变,所以Java很苦恼,即便是看似激进的JDK版本策略也敌不过臃肿守旧的印象。
怎么办?要兼容稳定,那么别打语法、API、字节码创新的心思,Java本身就那样了,但它背后的JVM却有更多的选择。Java的问题可以让JVM来补救,说资源占用高那先来个JPMS模块化(但目前看貌似并不成功),说启动延迟大那咱支持AoT搞Native吧,说对系统级应用、移动应用、敏捷应用支持不好那你行你上,我把你们都包进来纳入到我JVM大生态中,这就是GraalVM正在做的。
GraalVM简述
GraalVM是一个新的JVM,原本用于替换HotSpot的C2编译器,后来独立成JVM的一个产品,它很新但架不住对Native Image、多语言集成、高性能特性的诱惑,就连“后知后觉”的Spring也着手相关的支持工作,而新新的框架诸如quarkus、micronaut都已提供了比较好的支持。
但是问题来了,你说得这么好,为什么不见人用,国内外找了一圈都是些介绍性的文章?原因嘛,因为GraalVM要解决的问题有很多,现有的应用、框架都需要一定的改造。前面扯了这么多,接下来才是本文的重点:以实例切入带各位体验下GraalVM的集成改造。
实例:Dew-Common GraalVM集成
Dew-Common( https://github.com/gudaoxuri/dew-common )是笔者开源的一个Java基础工具包,包含了Json、Bean(反射)、Package Scan、JS交互、Shell调用等常用操作的支持,拿这个工具包做GraalVM的集成可以比较全面的检验集成的效果。
前置准备
安装GraalVM及相关的依赖,GraalVM支持Linux、Windows及MacOS,但一般推荐在Linux下操作。笔者使用的是Windows,Windows 10 2004版本的 WSL2 提供了完整的Linux内核,非常适合开发调试(Windows是最好的Linux发行版本😂)。
Note:详见 https://www.graalvm.org/getting-started/#install-graalvm
POM改造
一般情况下我们不需要引入额外的依赖,但如果需要执行跨语言操作就必须引入
graal-sdk
依赖,该依赖提供了GraalVM特有的语法API,注意scope为provided,即它只作用于编译、测试阶段,运行时不需要下面的几个包是用于跨语言操作的兼容处理,在GraalVM环境不需要,但在HotSpot必须引入
使用特定的profile执行Native Image打包操作
Native Image由GraalVM的SubstrateVM(定制轻量VM)运行,不需要第2步引入的兼容依赖,所以这里做了排除
调用
mvn test
附加执行参数,用于Native Image动态调用的代码收集,后文会细讲Native Image打包的核心插件,这里需要指定main方法,可指定镜像的名称
Tip:GraalVM没有集成 javax
包,所以如果需要诸如validation注解则需要手工引入 jakarta.validation-api
依赖
小结如下:
只是将程序运行在GraalVM下,那么只要把GraalVM缺失的依赖(如上面说的
javax
包)引入即可要做跨语言操作,那么完成第1、2步骤即可
要支持Native Image则必须完成后续的步骤
跨语言调用
JSR 223规范下脚本调用
用ScriptEngineManager定义脚本引擎管理器
添加对Java方法的调用支持
上面是JSR 223规范下的使用方式,使用了 nashorn
引擎,但在JDK11下已经标记过时,后期会移除,为什么移除?自然是因为了有GraalVM,Java官方也推荐使用GraalVM运行脚本。
GraalVM下的脚本调用
在语法层面变动比较大,但套路类似。
Note:Dew-Common相关代码详见 https://github.com/gudaoxuri/dew-common/blob/master/src/main/java/com/ecfront/dew/common/ScriptHelper.java
Note:GraalVM Polyglot 详见 https://www.graalvm.org/docs/reference-manual/polyglot/
Classpath相关
当我们打包成Native Image时GraalVM内置的SubstrateVM对Classpath相关的操作需要注意,这里举几个例子:
我们还需要注意在Native Image中好像没有package的概念( https://github.com/oracle/graal/issues/1108 ),导致我们无法对“jar包”做遍历,如 https://github.com/gudaoxuri/dew-common/blob/master/src/main/java/com/ecfront/dew/common/ClassScanHelper.java 下的 scan 就无法实现。
反射处理
看过GraalVM介绍的话大家都应该知道Native Image是基于静态代码可达分析,而对于反射方法的操作是无法自动发现的。这个影响很大,比如我们常用的BeanCopy、Json与Java对象的互转、动态代理等会有不同程度的限制。
这些动态调用需要我们来告诉GraalVM,GraalVM为我们提供了一个agent用于运行期自动收集相关的数据,收集时要确保所有动态调用都被执行到。
下面以Dew-Common为例子说明下如何操作:
所有相关的代码都写成单元测试
配置Native Image到/src/main/resources/META-INF/native-image/com.ecfront.dew/common/native-image.properties(InfoQ显示有问题,详见:https://www.idealworld.group/2020/06/12/getting-started-with-graalvm/)
配置Agent的过滤器到/src/main/resources/META-INF/native-image/com.ecfront.dew/common/agent-access-filter.json(InfoQ显示有问题,详见:https://www.idealworld.group/2020/06/12/getting-started-with-graalvm/)
为单元测试添加参数,更完整的见
POM改造
章节运行单元测试
上面的操作会在调用 mvn test -P native
后会把单元测试收集的包含反射、代理等动态操作写入config-output-dir指定的目录下。
这样我们可以配置 native-image-maven-plugin
(见POM改造章节) , 该插件默认会去 META-INF/native-image/<groupId>/<artifactId>/
找对应的Native Image配置及Agent收集信息,调用该插件 mvn package -P native
完成Native Image打包。
测试
经过上述操作,只要单元测试覆盖全面那么Native Image应该就可以正常工作了,但作为类库,我们还需要有集成测试以确保符合我们的预期。相关的操作可参见 Dew-Common it
目录下的测试工程。
总结
本文简单地介绍了GraalVM的使用,但GraalVM的Native Image目前并不完善,比如对Spring的支持还很有限,Spring有对应的 spring-graalvm-native
( https://github.com/spring-projects-experimental/spring-graalvm-native )工程,该工程还没有Release,问题很多。不过在今年晚些时候应该可以Ready,届时我们再一起体现下Spring Native的魅力。
关注我的公众号:
版权声明: 本文为 InfoQ 作者【孤岛旭日】的原创文章。
原文链接:【http://xie.infoq.cn/article/f05fe9812c2d22968c1b9b5f8】。文章转载请联系作者。
评论