Java AOT 之 GraalVM native image 介绍以及简单长连接服务实践
Java 语言有 20 多年的发展历史,拥有众多优秀的特性如面向对象、安全、解释性、平台无关等,该语言以及其强大的生态使其成为最重要的网络编程语言。但是随着近年来技术架构的发展,微服务逐渐趋向云原生及 Serverless 化,使得 Java 也面临如下挑战:启动缓慢、内存占用大、预热问题。
Java 服务启动时首先要启动一个 JVM 虚拟机,然后虚拟机会加载字节码,中间还包括类的加载解析初始化。JVM 运行字节码运行时有解释执行和编译两种执行方式:当系统刚启动时,JVM 会以解释执行并检测热点代码,热点代码会通过 c1c2 编译器进行编译成本地二进制代码。通过这样的机制,Java 实现了很多诸如反射、动态代理等运行时的机制,例如可以在程序运行过程中加载并编译一段代码,而这个在静态编译中却不可想象。运行时编译还有个好处是可以做比较激进的编译优化,通过 c2 编译器和分支预测 Java 在某些场景下可以表现出比 c++更强劲的性能。但这些优点也是有价的,包括启动耗时、预热耗时以及服务内存占用。
Graalvm
Graalvm 是 Oracle 公司提供的一个高性能、云原生、多语言的虚拟机。除了运行 Java 和基于 JVM 的语言之外,GraalVM 的语言实现框架 (Truffle)使得在 JVM 上运行 JavaScript、Ruby、Python 和许多其他流行语言成为可能。借助 GraalVM Truffle,Java 和其他支持的语言可以直接互操作,并在同一内存空间中来回传递数据。
Native Image 是一种将 Java 代码提前编译为独立可执行文件的技术,此刻执行文件包括应用程序类、依赖、运行时库以及 JDK 静态连接的本机代码。Graalvm 通过子模块 SubstrateVM 来支持 Native Image,相比 JVM 其生成的程序具有更快的启动时间和更低的运行时开销。
SubstrateVM
SubstrateVM 是 GraalVM 实现静态编译的基础,可以从支持静态编译以及运行时两方面来简单了解。
静态编译
应用程序、第三方库和 JDK 字节码共同组成了静态编译的输入,SubstrateVM 会对输入进行静态分析,找到其中可达代码,然后可达代码将会有静态编译器进行编译,最终得到 native image。值得注意的是由于只会编译可达的代码,所以其生成的文件相对会较小。静态分析输出是控制流图(Control Flow Graph)和类型流图(Type Flow Graph),其耗时也为整个编译流程中最长。
运行时
Native image 运行需要提供垃圾回收、类初始化检查、异常处理、多线程等支持。SubstrateVM 通过 Java 做轻量化 VM 运行时实现。并且通过静态编译,将运行时支持一起编译至 native image 中,需要注意的是 GraalVM 社区版只提供 SerialGC 垃圾收集器。
实践
通过安装使用 GraalVM 社区版,并创建一个 echo 的长连接服务来实践 GraalVM native image,这里的环境是 MacOS。
安装 GraalVM 社区版
通过 Github 下载 GraalVM
解压到/Library/Java/JavaVirtualMachines 目录下
打开 JavaVirtualMachines 目录下 Contents/Home/bin 校验 java -version,添加 JAVA_HOME 环境变量
安装 native-imagegu
gu install native-image,编译 native image 依赖于本地工具链,确保本地有安装 glibc-devel、zlib-devel、gcc
长链服务
服务通过 Netty 实现一个 echo 服务器,主要包括以下类
启动类
Handler
Decoder
Encoder
pom.xml
先启动服务,使用 nc 工具测试功能,结果如下。
然后关闭服务器,用 GraalVM 执行静态编译,这里使用的是 maven 插件,执行 mvn -Pnative -DskipTests package 即可完成字节码编译及静态编译,编译结果如下,可以看到整个静态编译耗时还是比较长的,主要分布在静态分析和编译这两个阶段。
生成的 native 文件位于 target 包中,大小约 17M,进入 target 目录,可用./mini-connector 直接执行,用 nc 测试发送 hello 然后关闭服务器,结果如下
结论
通过 GraalVM native image,可以将 java 服务启动时间压缩数十倍,且生成的二进制文件大小也优于包括所有依赖的 jar 包。
与传统 Java 运行模型相比,静态编译运行通过 AOT 避免了 JIT 的 CPU 开销,也避免了传统运行模型中一定存在的解释执行问题,使得程序性能较稳定。通过轻量化 SubstrateVM 实现,且也静态编译至 native image 中,提供了较快的 vm 性能和启动速度。
但是,任何技术都有优缺点。而 Graalvm 静态编译则需要面临解决动态类加载、反射、动态代理等动态特性的适配问题。另外通过 native 运行的程序,将不再适用面向传统 JVM 程序的调试、监控、Agent 等功能。
版权声明: 本文为 InfoQ 作者【BUG侦探】的原创文章。
原文链接:【http://xie.infoq.cn/article/542538a54048d82ba64e9183b】。文章转载请联系作者。
评论