GraalVM is used to eliminate cold start and high runtime memory issues for Java applications through static compilation. For GraalVM applications, Application Real-Time Monitoring Service (ARMS) provides a static instrumentation solution that shifts the bytecode modification logic used by the ARMS agent for Java at runtime to static compilation. This achieves static enhancement and out-of-the-box observability capabilities.
We recommend that you deploy the cutting-edge GraalVM applications first in a test environment. You can join the DingTalk group (ID: 80805000690) to obtain technical support.
Limits
The application must be configured to adapt to static compilation. If you are using a Spring Boot application, you can configure the application by referring to Spring Boot documentation.
Use the GraalVM JDK provided by ARMS when you perform static compilation.
GraalVM static compilation has specific requirements for the environment. For more information, see GraalVM documentation.
ARMS provides tracing and metric monitoring for GraalVM applications. Arthas, continuous diagnostics, and memory snapshots are not supported. The JVM monitoring-specific metadata details, non-heap memory, and direct buffer have no available data because GraalVM applications have different memory management from mainstream JVM applications.
Procedure
Step 1: Install dependencies
Install the required dependencies in the environment.
Download the ARMS agent based on the region where your application resides.
The ARMS agent is available in the following regions. To obtain technical support, join the DingTalk group (ID: 80805000690).
Region
Public URL
VPC address
China (Hangzhou)
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
China (Shanghai)
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
China (Beijing)
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
China (Zhangjiakou)
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
China (Shenzhen)
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
China South 1 Finance
None
wget "http://arms-apm-cn-shenzhen-finance-1.oss-cn-shenzhen-finance-1-internal.aliyuncs.com/ArmsAgentNative.zip" -O ArmsAgentNative.zip
Decompress the installation package of the ARMS agent, go to the ArmsAgentNative directory, and then run the following command to install it in the environment.
sh install.sh
Download the GraalVM JDK (graalvm-java17-23.0.4-ali-1.2b.tar.gz), which provides observability.
Decompress the file and run the following command in the directory.
graalvm-java17-23.0.4-ali-1.2b/bin/native-image --version
If the following result is returned, the installation is successful.
Download Maven (apache-maven-3.8.4-bin.tar.gz). If you have installed Maven, proceed to the next step.
Decompress the file, set the JAVA_HOME variable to the path of GraalVM and the MAVEN_HOME variable to the path of Maven.
Specify
/xxx/
in the sample commands.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
Step 2: Add dependencies
Add the following dependencies to the application.
Replace /xxx/dynamic-configs
in the code to the path of the dynamic configuration file of the application.
<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>
Step 3: Add the access-filter-file.json file
Add the access-filter-file.json file to the directory of the application, which contains the following:
{ "rules": [
{"excludeClasses": "sun.launcher.LauncherHelper"}
]
}
The access-filter-file.json file prevents the agent that collects dynamic properties in GraalVM from collecting reflections in sun.launcher.LauncherHelper
. The sun.launcher.LauncherHelper
is a class used during JVM startup, and its reflections are not required for the statically compiled Native Images. If the reflections are collected, compilation errors will occur.
Step 4: Pre-execute the application
To ensure that the dynamic enhancement code injected by the ARMS agent is statically compiled into the final Native Image file, you need to mount the agent and execute the application in advance. When you pre-execute the application, you must ensure that all core code branches of the application are executed. A script is provided to help you with the pre-execution. Note that all RESTful interfaces of the application must be declared in the script as prompted, so that they can be properly called and trigger business execution.
Modify the script based on your needs.
######## Modify the parameters # The ARMS integration-specific parameters. The value of LicenseKey can be obtained by calling the DescribeTraceLicenseKey operation. Set the value of AppName to the application name. In a distributed architecture, an application can contain multiple peer application instances. export ARMS_LICENSEKEY= export ARMS_APPNAME= # The list of application interfaces. Sample value: (interface1 interface2 interface3 interface4). export PS= # The application port. Sample value: 8080. export PORT= # The path where you want to store the Native Image file in the target directory of the application after static compilation. Sample value: target/graalvm-demo. export NATIVE_IMAGE_FILE= # The command for executing the ARMS agent. Sample command: -javaagent:./arms-native/aliyun-java-agent-native.jar -jar target/graalvm-demo-1.0.0.jar. export JAVA_CMD= ########
Mount the ARMS agent to start pre-execution and collect configuration items of static compilation.
sh ArmsAgentNative/run.sh --collect --jvm --Carms
Step 5: Statically compile the application
Perform the following steps to compile the application statically.
Start static compilation.
mvn -Pnative package
Run the compiled project.
sh ArmsAgentNative/run.sh --native --Carms
Related operations
Build a Docker image
If you want to build a Docker image for a statically compiled GraalVM application, you can place the final Native Image file as an executable in the image and then follow the normal Docker image build process because the application already includes all the necessary runtime information such as the JDK.
Sample Dockerfile:
Replace the -Darms.licenseKey
and -Darms.appName
parameters based on your needs.
FROM centos:latest
WORKDIR /app
COPY ./target/graalvm-demo /app
CMD ["/app/graalvm-demo","-Darms.licenseKey=xxx","-Darms.appName=xxx"]
Compress a Native Image
When the sizes of a Native Image and Java program are compared, the JDK is generally considered to be part of the Java program. This is because Java programs require JDK support, whereas Native Images are self-contained and already include all dependencies.
However, as a Native Image consists of assembly code, which has a lower information density compared to the bytecode of a Java application, more code is required to express the same semantics. As a result, with an application scaling, the size of the Native Image may eventually exceed the combined size of the Java program and JDK. This causes increased pressure to deployment and transmission. In this case, we can use a compression tool such as UPX to reduce the size of a Native Image. UPX can compress binary executable files into smaller binary executables, and the compressed files can be run directly without decompression, with minimal impact on runtime performance.
The following figure shows compressing a Native Image.
Compress the statically compiled graalvm-demo
file into the graalvm-demo-compressed
file. The size of the latter is 28.4% the size of the former.
The following figure compares the sizes of graalvm-demo, graalvm-demo-compressed, and the Fat Jar.
The size of the Fat JAR that contains all dependencies of Spring Boot and RocketMQ is 216 MB, whereas the size of the compressed Native Image is only 47 MB.
Perform the following steps to use UPX:
Download and decompress the installation package of UPX.
Suppose UPX resides in the
$UPX_HOME
directory after decompression. Run the following command to compress a file:$UPX_HOME/upx -9 -o path/to/output-file path/to/original-file
-9
: the compression level, ranging from 1 to 9. Higher values result in higher compression ratios but require longer compression duration.-o path/to/output-file
: the path of the compressed output file. Replace path/to/output-file with the actual file path.path/to/original-file
: the path of the original file. Replace path/to/original-file with the actual file path.