Supercharging Micronaut Applications with GraalVM
Unleashing the Power of Ahead-of-Time Compilation
What is GraalVM?
GraalVM is an innovative and high-performance JDK developed by Oracle, specifically designed to enhance the performance of Java applications while minimizing resource consumption.
This versatile platform offers two distinct methods for running Java applications:
JIT (Just-in-Time) Compilation
GraalVM integrates seamlessly with the HotSpot JVM, leveraging its Graal JIT compiler. This dynamic compilation approach optimizes the execution of Java bytecode at runtime.
Native Image Compilation
Another powerful feature of GraalVM is its ability to generate ahead-of-time (AoT) compiled native executables. With this approach, Java applications can be compiled ahead of time into highly optimized machine code, eliminating the need for a JVM during execution.
Besides Java, it provides runtimes for JavaScript, Ruby, Python, and a number of other popular languages. GraalVM’s polyglot capabilities make it possible to mix programming languages in a single application.
Runtime Modes
GraalVM is unique as a runtime environment offering several modes of operation: JVM runtime mode, Native Image, and Java on Truffle (the same Java applications can be run on either).
JVM Runtime Mode
When executing programs on the HotSpot JVM, GraalVM takes advantage of its powerful compiler, known as the GraalVM compiler, which serves as the default top-tier JIT compiler. During runtime, applications are loaded and executed in the standard JVM environment. The JVM receives bytecode, whether it’s for Java or any other language supported by the JVM, and passes it to the GraalVM compiler for optimization.
Native Image
Native Image is an innovative technology that compiles Java code into a standalone native executable or a native shared library. During the build process of a native executable, it processes the Java bytecode, which includes all application classes, dependencies, third-party libraries, and necessary JDK classes. The resulting native executable is self-contained, specific to the operating system and machine architecture it was built for and does not rely on a JVM for execution. This means the generated native executable can be run directly without needing a separate JVM installation.
Java on Truffle
The Java on Truffle execution mode enables the execution of Java code through a Java bytecode interpreter implemented using the Truffle framework. The Truffle framework, an open-source library designed for developing interpreters for various programming languages, facilitates the implementation of a performant and efficient interpreter for Java bytecode. This execution mode leverages the capabilities of Truffle to provide a seamless and reliable environment for executing Java code without the need for a traditional JVM-based execution model.
GraalVM Native Image
Native Image is a technology to compile Java code ahead of time to a binary — a native executable. A native executable includes only the code required at run time, that is, the application classes, standard library classes, the language runtime, and statically linked native code from the JDK.
- Compiles Java code ahead of time to a binary — a native executable.
- It includes Substrate VM — a slim VM implementation that provides runtime components, such as a garbage collector and a thread scheduler.
- It includes only the code required at run time, e.g. Application classes and standard library classes.
- Ideal for containers and cloud deployments.
Advantages of Native Image
- Uses a fraction of the resources required by the Java Virtual Machine, so it is cheaper to run.
- Starts in milliseconds.
- Delivers peak performance immediately, with no warm-up.
- It can be packaged into a lightweight container image for fast and efficient deployment.
Micronaut Support for GraalVM native Image
Any Micronaut application can be run using the GraalVM JVM; however, special support has been added to Micronaut to support running Micronaut applications using GraalVM’s native-image tool.
Since Micronaut 2.2, any Micronaut application can be built into a native image using:
- Gradle
- Maven plugins
Micronaut itself does not rely on reflection or dynamic class loading, so it works automatically with GraalVM native; however, certain third-party libraries used by Micronaut may require additional input about the uses of reflection. For such requirements, Micronaut includes an annotation processor that helps to generate reflection configuration that is automatically picked up by the native-image tool.
For a Micronaut application/microservices, the native image can be created with the following approaches:
Using Docker
$mvn package -Dpackaging=docker-native
Without Using Docker
- Installing GraalVM.
- Installing native-image tool.
mvn package -Dpackaging=native-image
Setting up GraalVM and native Image
Install GraalVM by following the instructions on this link: Install GraalVM.
- Run the below command to generate a native image from the root directory or folder.
$mvn package -Dpackaging=native-image
- Post successful execution of the above command, the native executable will be created in the target directory.
$ls /target/micronautguide
- Run the native image executable below.
$./target/micronautguide
Here is what you see after running the native image executable.
- You can execute the endpoint exposed by the native executables.
$curl localhost:8080/hello
Creating native executable inside Docker
- Run the below command to generate a Docker image from the root directory or folder.
$mvn package -Dpackaging=docker-native
On successful execution, the above command should create a Docker image on the local machine.
- Do verify with
docker images
command, it should create an image with the namemicronautguide
.
$docker images
- Run the Docker image.
$docker run -p 8080:8080 micronautguide
- You can execute the endpoint exposed by the native executable.
$curl localhost:8080/hello
Conclusion
GraalVM’s broad language support and polyglot capabilities empower developers to build versatile applications by seamlessly combining multiple programming languages. This flexibility, coupled with performance optimisations, encourages collaboration, code reuse, and the utilization of the best tools and libraries from different language ecosystems, ultimately enhancing development productivity and application performance.