CRaC Compared to Native
With the SpringBoot PetClinic application, a comparison was made between CRaC and a native image, created with GraalVM Community Edition and SpringBoot Native. Of course these results will vary depending on the system and for other applications, but they can be used as a guideline.
Startup Time
This chart shows the result for the following scenarios:
-
Starting the application as a jar-file.
-
Restarting the application from an automatic checkpoint, created by SpringBoot Native right before the application is started. For more info, see Frameworks With CRaC Support > Spring and Spring Boot > Automatic Checkpoint.
-
Restarting the application from a mnaual checkpoint. For more info, see Frameworks With CRaC Support > Spring and Spring Boot > Manual Checkpoint.
-
Starting the application as a Native Image.
Memory Footprint
Deployment Artifact
To run a jar-file, a Java runtime is required. You can use the full JDK, but Azul provides JRE downloads with have a smaller size, as they don’t include build tools etc. Another option to reduce the runtime size, is using jlink
. With this tool you can create a custom runtime that only includes the Java modules needed by your application.
The use of jlink depends on the modules in your application, the environment, the required Java version, etc. You can use the following script, based on the PetClinic application used for the measurements on this page, as a guideline to generate a runtime with jlink
:
#!/bin/bash
JAVA_VERSION=21
MAIN_JAR="spring-petclinic-3.2.0.jar"
APP_VERSION=3.2.0
echo "Java home: $JAVA_HOME"
echo "Project version: $PROJECT_VERSION"
echo "App version: $APP_VERSION"
echo "Main JAR file: $MAIN_JAR"
# ------ SETUP DIRECTORIES AND FILES ----------------------------------------
# Remove previously generated java runtime and installers. Copy all required
# jar files into the input/libs folder.
rm -rfd ./build/java-runtime/
rm -rfd build/installer/
mkdir -p build/installer/input/libs/
cp build/libs/* build/installer/input/libs/
cp build/libs/${MAIN_JAR} build/installer/input/libs/
# ------ REQUIRED MODULES ---------------------------------------------------
# Use jlink to detect all modules that are required to run the application.
# Starting point for the jdep analysis is the set of jars being used by the
# application.
echo "Detecting required modules"
detected_modules=`$JAVA_HOME/bin/jdeps \
--multi-release ${JAVA_VERSION} \
--ignore-missing-deps \
--print-module-deps \
--class-path "build/installer/input/libs/*" \
build/classes/java/main/org/springframework/samples/petclinic/PetClinicApplication.class`
echo "Detected modules: ${detected_modules}"
# ------ MANUAL MODULES -----------------------------------------------------
# some modules have to be added manually bound via --bind-services or
# otherwise e.g. HTTPS does not work.
#
# See: https://bugs.openjdk.java.net/browse/JDK-8221674
#
# In addition we need jdk.localedata if the application is localized.
# This can be reduced to the actually needed locales via a jlink parameter,
# e.g., --include-locales=en,de.
manual_modules=jdk.crac,jdk.crypto.ec,jdk.localedata,java.sql,java.xml,java.logging,java.management,java.compiler,java.naming,java.rmi,java.instrument,java.security.jgss,java.net.http,jdk.httpserver,jdk.naming.dns,java.desktop,java.transaction.xa
echo "Manual modules: ${manual_modules}"
# ------ RUNTIME IMAGE ------------------------------------------------------
# Use the jlink tool to create a runtime image for our application. We are
# doing this is a separate step instead of letting jlink do the work as part
# of the jpackage tool. This approach allows for finer configuration and also
# works with dependencies that are not fully modularized, yet.
echo "Creating Java runtime image"
$JAVA_HOME/bin/jlink \
--no-header-files \
--no-man-pages \
--compress=2 \
--strip-debug \
--add-modules "${detected_modules},${manual_modules}" \
--include-locales=en \
--output build/java-runtime
With this script, and the PetClinic sources, you can execute the application locally with the reduced Java runtime with the following commands:
# Use SDKMAN to configure JDK 21
sdk use java 21.0.4-zulu
# Build the jar file
./gradlew build
# Build the reduced Java runtime
bash jlink_script.sh
# Test-run the build and runtime
build/java-runtime/bin/java -jar build/libs/spring-petclinic-3.2.0.jar
The final size of the generated runtime with jlink
will be different for each use case. With the Java version and modules of the test application used for this measurement, we reduced the size from 150Mb to 70Mb:
Checkpoint Size
As you can read in Usage Guidelines > Using Image Compression on Checkpoint, the size of the created checkpoint can be reduced in case you need to save some space in return for a relatively small increase in time on restore. The following chart compares the size of the uncompressed and compressed checkpoints.
The total sizes in this chart apply to the deployment package. This is an important metric for the up- and downloads into, for instance, virtualized environments that need to be started when your application scales up. The smaller the size, the faster and better startup time.
Note
|
A compressed checkpoint gets decompressed at startup (restore) to its original size. This increases the startup time by up to 150ms. |
Conclusion
CRaC is not only as fast as a native image related to startup time, it also has a very similar memory footprint. On top of that, the CRaC version, can still make profit of all the improvements offered by the JVM, for instance, further compiler improvements of the use of the application changes.