Announcing native image support for Java Client Libraries — Optimize your short lived workloads
Java Developer Advocate
We are excited to announce that Cloud Java Client Libraries now have built-in support for Native Image compilation!
Native Image technology enables you to compile your Java applications ahead-of-time and into a standalone executable. This results in several performance benefits, such as fast cold startup times and less upfront memory usage (as it doesn’t require a JVM).
However, Native Image compilation isn’t always compatible with some forms of Java code (resource loading, reflection) and requires extra configuration. With this launch, Cloud Client Libraries now come with the configuration that the libraries need for native image compilation, allowing users to compile their applications without additional configurations. It is also important to note that with this technology, you lose the JVM's run time optimizations, making native compilation best suited for short-lived workloads where quick startup and response time is key.
We conducted a performance comparison of an application built as a native image against the same application run with standard Java (17.0.3, Temurin) and noticed the following benefits.
The performance gap shown above is significant, especially when just comparing startup times. In this example, 87.65% of the native image start up times came in under 1 millisecond, which would make an enormous difference when aiming to optimize for cold start latency.
Memory usage for the application compiled to a native image is also significantly smaller. We used the ps command to check the resident set size, which is the non-swapped physical memory that a task has used, and saw the following results:
This section will walk you through running the Pub/Sub Storage Sample with native image compilation.
To demonstrate the performance benefits unlocked by the client library support of native image compilation we’ll build a sample application that makes use of the Pub/Sub and Storage client libraries, informed by this guide. Feel free to follow along on your own machine, or with the Cloud Shell.
To reproduce the application that we used to gather the performance data above, you will need:
Building the sample app
Start by generating our project with the following Maven goal:
This will generate a new Maven project that will serve as a reasonable starting point for our sample app.
Start by setting the maven compiler plugin’s release version to 17 (or later) and adding the client libraries as dependencies:
Add a class called ListPubSubNotifications in the same directory as as App.java:
This class will simply list any Pub/Sub notifications that are set up for a given Storage bucket.
Next, add another class in the same package called PublishWithErrorHandlerExample:
This class will publish a timestamp message to a given topic, and handle exceptions in case of failure.
Finally, in App.java’s main function, add some logic to call the functions in both classes and keep track of startup/execution time:
Now that the sample application is in place you are ready to configure the native image build.
Add the following `native-image` build profile to your pom.xml:
This plugin and profile simplify the process of providing the native-image builder with the configuration it needs to build your application into a native image. Ensure that the `mainClass` parameter is correct for your application, and note that `buildArgs` can be used to pass options to the builder.
At this point, your app is ready to be built into a native image. There are, however, a few things worth keeping in mind with native-image’s ahead-of-time builds:
They take longer than equivalent just-in-time builds (approx. 5-10 minutes*)
They take quite a bit of memory (approx. 6-10GB*)
* values come from building this sample on the Cloud Shell’s e2-standard-4 machine type
To run the build, we will need to ensure that we have an appropriate JDK and the native-image builder. This process can be greatly simplified with the help of SDKMAN!.
Install the appropriate JDK distribution like GraalVM with the following command:
Then, inform sdkman to use this version in your current shell:
Next, install the native-image extension:
Finally, run the build the native-image-client-libraries-sample project with the `native-image` profile:
This build process may take a few minutes. Once the build finishes, you will have everything you need to see the performance differences in action!
The generated executable is in the "target" directory. Run the program to see it receives notifications from the Cloud Storage bucket
In this example, the native image started up 159x faster than the regular jar, and finished 19 times faster as well. The results of native image compilation can vary greatly depending on the workload, so feel free to experiment with your own applications to get the most out of your cloud resources.
This is only one example of how native image compilation is supported in Cloud Java Client Libraries. Please check out our official documentation to learn more about what libraries are supported and how you can build applications as native images. The documentation page also links to a couple of samples that you can try out.