GraalVM - 云原生时代的 Java 笔记
- 前言
- GraalVM 诞生的背景
- GraalVM
- Graal Compiler
- Benefits of JIT
- Creating a Native Image
- 限制
- Configuring Reflection Configuration
- Static Configuration
- Dynamic Configuration via Agent
- Dynamic Configuration via Feature
- Spring Graal Native
前言
GraalVM 诞生的背景
Java 在微服务时代宏观上的困境:
- 一个事实: Java 总体上是面向大规模、长时间的服务端应用而设计的。严(luo)谨(suo)的语法利于约束所有人写出较一致的代码,利于软件规模的提升;即时编译器、性能制导优化、垃圾收集子系统等有代表性的特征都是面向程序长时间运行设计的,需要一段时间来达到最佳性能,也便于享受硬件规模提升的红利。
- 一个矛盾: 在微服务的背景下, 提倡服务围绕业务能力构建, 不再追求实现上严谨一致; 单个微服务就不再需要再面对数十、数百 GB 乃至 TB 的内存; 有了高可用的服务集群, 也无须追求单个服务要 7*24 小时不可间断地运行, 它们随时可以中断和更新。但微服务对应用的容器化亲和度(包容量、内存消耗等)、启动速度、达到最高性能的时间等方面提出了新的需求, 这些又正好是 Java 的弱项。
问题的根源:
- Java 是 VM Base 而不是 Native Base 的
- Java 的代码域是动态的、开放的, 而不是静态的、封闭的
解决方案
- 革命派: 直接革掉 Java 和 Java 生态的性命, 创造新世界, 譬如 Golang。
- 激进派: 摒弃重负载的传统 Java 生态, 在 GraalVM 上另起炉灶开发新的 Java 应用, 譬如 Quarkus、Micronaut。
- 温和派: 尽可能保留原有主流 Java 生态和技术资产, 尽可能通过技术手段自动化地把遗留代码升级成为 GraalVM Native 应用。
- 保守派: 在原有的 Java 生态上做改进, 朝着微服务、云原生环境靠拢、适应, 譬如 CNFC Buildpack。
GraalVM
GraalVM 是 Oracle 新一代的多用途(Universal)、多语言(Polyglot)的虚拟机, 其中包括的主要技术组件有:
- Increase application throughput and reduce latency
高性能的即时编译器和提前编译器 Graal Compiler - Compile applications into small self-contained native binaries
代替 HotSpotVM 的微型运行环境 SubstrateVM - Seamlessly use multiple languages and libraries
以 Truffle 和 Sulong 为代表的中间语言解释器
Graal Compiler
- 最初(2012 年之前)是在 Maxine 虚拟机中作为 C1X 编译器的下一代编译器而设计的, 所当然地使用 Java 语言来。
- 高编译效率、高输出质量、同时支持提前编译(AOT)和即时编译(JIT), 同时支持应用于包括 HotSpot 在内的不同虚拟机的编译器。
- 与 C2 采用一样的中间表示形式(Sea of Nodes IR), 后端优化上直接继承了大量来自于 HotSpot 的服务端编译器的高质量优化技术, 是现在高校、研究院和企业编译研究实践的主要平台了。
- Graal Compiler 是 GraalVM 和 HotSpotVM (从 JDK10起)共同拥有的服务端即时编译器, 是 C2 编译器未来的替代者。
- VM base VS Native Base
- JIT(HotSopt) VS AOT(GraalVM Native)
Benefits of JIT
- Profile-Guided Optimization, PGO
- Aggressive Speculative Optimization
- Link-Time Optimization
- …
Creating a Native Image
- 与原本 Java 一样编译程序
- 使用 GraalVM 的 native-image 命令生成一个本地镜像(大坑警告)
有限制, 并不能支持 Java 中所有的特性
如果没有其他辅助手段的话, 需要大量的配置信息
需要大量的 CPU 和内存资源
限制
- Not Supported
Dynamic Class Loading/Unloading
Runtime Bytecode Generation
InvokeDynamic Bytecode and Method Handles
Finalizers
Serialization
Security Manager
JVMTI、JVMCI, other native VM interfaces - Require Configuration
Resource Access
Reflection
Dynamic Proxy(JDK, not CGLIB)
JNI(Java Native Interface)
JCA(Java Cryptography Architecture)
Configuring Reflection Configuration
- reflection-config.json
[
{
name: "com.example.Something",
allDeclaredConstructors: true,
allPublicMethods: true
},
{
name: "com.example.SomethingElse",
fields: [
{
name: "value"
}
],
methods: [
{
name: "<init>",
parameterTypes: [
"char[]"
]
}
]
}
]
Static Configuration
- Point at the config files directly with native-image options:
-H:ReflectionConfigurationResources=/path/reflection-config.json \
-H:DynamicProxyConfigurationResources=/path/dynamic-proxies.json \
...
- Or, place files in well known locations on the classpath:
META-INF/native-image/
META-INF/native-image/<groupId>/<artifactId>
...
Dynamic Configuration via Agent
- Agent can assist in generating these files, available with GraalVM
$ java -agentlib:native-image-agent=config-output-dir=META-INF/native-image Demo
- But…
Doesn’t address initialization though, exercise all code paths(manually or via tests)
Dynamic Configuration via Feature
- Create a class implementing the Graal Feature interface
com.oracle.substratevm:graal-hotspot-library - Register new entries for reflection/resource/proxies/initialization handling
- Add @AutomaticFeature to ensure picked up from classpath automatically
Spring Graal Native