GraalVM技术通过为Java应用进行静态编译,帮助应用消除了冷启动和运行时内存占用高的问题。针对GraalVM应用,ARMS提供了静态插装方案,将应用运行时使用Java探针对字节码的改写逻辑调整到静态编译中,实现静态增强,提供开箱即用的可观测能力。
GraalVM作为一项前沿技术,如果之前未在生产场景使用过,建议您在测试环境充分验证后再考虑生产使用。此外,您在使用该方案接入ARMS过程中有任何问题,欢迎通过钉钉答疑群(群号:80805000690)与我们联系。
使用限制
- 应用本身已完成GraalVM静态编译适配,如果是Spring Boot应用,可以参考相关文档进行应用适配改造。 
- 该方案需要调整GraalVM JDK实现,因此使用时需确保所使用的GraalVM JDK均为ARMS提供。 
- 如果要使用GraalVM静态编译,其对环境有一些要求,具体要求请参见GraalVM官方文档。 
- 目前针对GraalVM场景,ARMS仅支持核心的Traces和Metrics功能,暂不支持Arthas、持续剖析和内存快照等功能,另外由于GraalVM场景下,应用内存形态有变化,因此JVM监控中的元空间详情、非堆内存和直接缓冲区无数据是正常的。 
接入操作
步骤一:安装依赖
GraalVM场景下,首先需要在环境中安装以下依赖:
- 根据自身应用所在地域,下载对应的GraalVM版本的ARMS探针。 - 当前仅支持以下地域,如有更多需求,请通过钉钉答疑群(群号:80805000690)与我们联系。 - 地域 - 公网地址 - VPC地址 - 华东1(杭州) - wget "http://arms-apm-cn-hangzhou.oss-cn-hangzhou.aliyuncs.com/ArmsAgentNative.zip" -O ArmsAgentNative.zip- wget "http://arms-apm-cn-hangzhou.oss-cn-hangzhou-internal.aliyuncs.com/ArmsAgentNative.zip" -O ArmsAgentNative.zip- 华东2(上海) - wget "http://arms-apm-cn-shanghai.oss-cn-shanghai.aliyuncs.com/ArmsAgentNative.zip" -O ArmsAgentNative.zip- wget "http://arms-apm-cn-shanghai.oss-cn-shanghai-internal.aliyuncs.com/ArmsAgentNative.zip" -O ArmsAgentNative.zip- 华北2(北京) - wget "http://arms-apm-cn-beijing.oss-cn-beijing.aliyuncs.com/ArmsAgentNative.zip" -O ArmsAgentNative.zip- wget "http://arms-apm-cn-beijing.oss-cn-beijing-internal.aliyuncs.com/ArmsAgentNative.zip" -O ArmsAgentNative.zip- 华北3(张家口) - wget "http://arms-apm-cn-zhangjiakou.oss-cn-zhangjiakou.aliyuncs.com/ArmsAgentNative.zip" -O ArmsAgentNative.zip- wget "http://arms-apm-cn-zhangjiakou.oss-cn-zhangjiakou-internal.aliyuncs.com/ArmsAgentNative.zip" -O ArmsAgentNative.zip- 华南1(深圳) - wget "http://arms-apm-cn-shenzhen.oss-cn-shenzhen.aliyuncs.com/ArmsAgentNative.zip" -O ArmsAgentNative.zip- wget "http://arms-apm-cn-shenzhen.oss-cn-shenzhen-internal.aliyuncs.com/ArmsAgentNative.zip" -O ArmsAgentNative.zip- 华南1金融云 - 无 - wget "http://arms-apm-cn-shenzhen-finance-1.oss-cn-shenzhen-finance-1-internal.aliyuncs.com/ArmsAgentNative.zip" -O ArmsAgentNative.zip- 解压文件后,进入ArmsAgentNative目录,执行以下命令在本地环境中安装探针。 - sh install.sh
- 下载支持可观测能力的GraalVM JDK版本:graalvm-java17-23.0.4-ali-1.2b.tar.gz。 - 解压后在对应目录中执行以下命令: - graalvm-java17-23.0.4-ali-1.2b/bin/native-image --version- 返回结果如下表示安装成功:  
- 下载Maven(如果环境已有Maven,无需再次安装):apache-maven-3.8.4-bin.tar.gz。 - 解压后,将环境变量JAVA_HOME和MAVEN_HOME设置对应解压文件后的路径。 - 例如: - 请将 - /xxx/换成实际路径。- export MAVEN_HOME=/xxx/apache-maven-3.8.4 export PATH=$PATH:$MAVEN_HOME/bin export JAVA_HOME=/xxx/graalvm-java17-23.0.4-ali-1.2b export PATH=$PATH:$JAVA_HOME/bin
步骤二:引入依赖
为应用添加如下依赖:
请将代码中的路径/xxx/dynamic-configs指向应用原始的动态配置文件地址。
<dependencies>
  <dependency>
   <groupId>com.alibaba.cloud</groupId>
   <artifactId>arms-javaagent-native</artifactId>
   <version>4.1.11</version>
   <type>pom</type>
 </dependency>
</dependencies>
<profiles>
  <profile>
    <id>native</id>
    <build>
      <plugins>
        <plugin>
          <groupId>org.graalvm.buildtools</groupId>
          <artifactId>native-maven-plugin</artifactId>
          <extensions>true</extensions>
          <executions>
            <execution>
              <id>build-native</id>
              <goals>
                <goal>compile-no-fork</goal>
              </goals>
              <phase>package</phase>
            </execution>
          </executions>
          <configuration>
            <fallback>false</fallback>
            <buildArgs>
              <arg>-H:ConfigurationFileDirectories=native-configs,/xxx/dynamic-configs</arg>
            </buildArgs>
          </configuration>
        </plugin>
      </plugins>
    </build>
  </profile>
</profiles>步骤三:添加access-filter-file.json文件
在应用目录下,添加一个名为access-filter-file.json的文件,其中内容为:
{ "rules": [
  {"excludeClasses": "sun.launcher.LauncherHelper"}
]
}添加access-filter-file.json文件用于确保GraalVM收集动态特性的探针不要收集sun.launcher.LauncherHelper中的反射。sun.launcher.LauncherHelper是JVM启动的类,其中用到的反射对于静态编译后的Native Image是不需要的,否则会在编译时产生错误。
步骤四:预执行
为了让ARMS探针对应用的动态增强代码被静态编译到最终的Native Image文件中,因此需要预先挂载ARMS探针对应用进行预执行,预执行需要确保应用核心代码分支都被执行,当前已提供一个相关脚本供您完成该过程。注意将应用的所有RESTful接口都按提示声明在脚本中,以便执行过程中的相关接口被正常调用触发业务执行。
- 参考以下脚本,按照注释完成自定义内容补充。 - ######## 请根据实际修改一下参数 # ARMS接入参数, 可调用DescribeTraceLicenseKey接口获取到对应的LicenseKey。AppName代表接入到ARMS的哪一个应用中,您可以根据需要自定义您的应用名,在分布式架构中,同一个应用内可以包含多个对等的实例。 export ARMS_LICENSEKEY= export ARMS_APPNAME= # 应用接口列表,例如PS=(interface1 interface2 interface3 interface4) export PS= # 应用端口,例如PORT="8080" export PORT= # Native image文件路径,静态编译后会在应用target目录下生成一个native image文件,例如NATIVE_IMAGE_FILE="target/graalvm-demo" export NATIVE_IMAGE_FILE= # 运行ARMS Native Agent的命令,例如JAVA_CMD="-javaagent:./arms-native/aliyun-java-agent-native.jar -jar target/graalvm-demo-1.0.0.jar" export JAVA_CMD= ########
- 挂载ARMS Java探针,进入预执行,搜集静态编译的配置项。 - sh ArmsAgentNative/run.sh --collect --jvm --Carms
步骤五:静态编译
完成依赖添加后,按照如下步骤对应用进行静态编译:
- 开始静态编译。 - mvn -Pnative package
- 运行静态编译后的项目。 - sh ArmsAgentNative/run.sh --native --Carms
其他操作
构建Docker镜像
如果要针对完成GraalVM静态编译后的应用构建Docker镜像,由于其中已经包含了JDK等运行时必要的信息,直接将最终的Native Image文件作为一个可执行文件放到镜像中,按照正常构建Docker镜像流程进行镜像构建即可。
示例Dockerfile:
-Darms.licenseKey和-Darms.appName参数需调整为实际值。
FROM centos:latest
WORKDIR /app
COPY ./target/graalvm-demo /app
CMD ["/app/graalvm-demo","-Darms.licenseKey=xxx","-Darms.appName=xxx"]压缩Native Image
在比较Native Image与原Java程序所占的空间大小时,我们会将JDK的大小也加在Java程序侧一起计算。因为Java程序必须要JDK支持,而Native Image是自举的,它已经包含了所有的依赖。
但是因为Native Image中为汇编代码,相比Java应用的字节码信息密度更低,表达相同的语义所需的代码量更大。因此随着应用规模的增大,Native Image的大小会逐渐超越Java程序 + JDK的大小,导致部署、传输的压力增大。此时我们可以借助压缩工具UPX来降低Native Image的大小。UPX可以将二进制可执行文件仍然压缩为二进制可执行文件,压缩后的文件无需解压即可直接运行,并且基本不会影响运行时的性能。
下图展示了压缩Native Image的效果:

将静态编译后的graalvm-demo文件压缩为graalvm-demo-compressed文件,压缩后的大小仅有之前的28.4%。
与Fat Jar包的比较:

原先包含了Spring Boot、RocketMQ等所有依赖的Fat Jar包大小为216MB,而压缩后的Native Image仅有47MB。
UPX使用方法:
- 下载并解压UPX工具。 
- 假设UPX被解压到 - $UPX_HOME目录,执行以下命令完成压缩。- $UPX_HOME/upx -9 -o path/to/output-file path/to/original-file- -9:压缩程度为1~9,值越大压缩率越高,压缩所需时间越长。
- -o path/to/output-file:压缩输出文件路径,path/to/output-file需要换成实际的文件路径。
- path/to/original-file:被压缩的原始文件路径,path/to/original-file需要换成实际的文件路径。