Skip to main content

Spring on GraalVM

Topic: GraalVM                                                                                                  Level: Advanced

GraalVM - What?

GraalVM is a virtual machine for seamless interoperability and polyglot language compilation that offers highly optimized runtime executable binaries, speeding the application startup and reducing memory consumption ideal for microservices, serverless, and cloud workloads in general. Any source code running on the Java Hotspot VM can also run on the GraalVM.

Native Images - What?

Native Image is a technology by which a java code is compiled ahead of time into a binary executable, termed a native executable which doesn't require a JVM to run. The notion of the native executable is to have optimized machine code (packaging only the necessary reachable runtime application classes, standard library classes, language runtime, statically linked native code from JDK, and other resources making it a lightweight artifact) offering instantaneous startup on container-based and cloud deployments, instant peak performance and reduced memory consumptions.

Native Build Process Illustration

How?

Before digging into the details of how the native executable is optimized for high performance with a low memory footprint, we need to understand how the traditional code is compiled into machine code for execution.

The Hotspot JVM uses JIT (Just In Time) compiler for generating a machine code from the application code, it happens on the go as the program executes delivering optimizations on the fly by systematic recompilations, handled by the C1 JIT compiler. Further on frequent code executions, the C2 JIT compiler performs execution optimizations based on gathered profiling information about the code branch that is often executed the most, frequent loop executions and types that are used in polymorphic code.
  1. Interpretation and verification code
  2. Loading classes/resources/methods
  3. Compiling dynamically (speculative assumption optimizations)
  4. Collecting profiling information and storing it in memory
are the step performed by the JVM leveraging memory and CPU process for computations.

The GraalVM uses the Graal compiler, which performs static code analysis accounting in only the required runtime classes, libraries negating the unreachable object dependencies, and metadata associated to be loaded on the objects heap such there is a reduction in the memory footprint consequently the G1GC becomes optimized with the lesser object for mark and sweep.
The Graal compiler performs ahead-of-time (AOT) compilation during the build time (reachable static code scanning and binary creations) before the code execution thereby generating runtime platform-specific executables contributing fast startup as it is precomputed and compiled. The AOT transformation benefits from the eager loading of classes and fixed build time classpath.
  1. Points-to analysis - Determines the classes, methods, and fields that are reachable at runtime for native image inclusion. The analysis is performed iteratively and transitively on the even on the JDK library classes picking only the necessary for the application, creating a self-contained binary.
  2. Initialization at build time - The class initialization defaults to the runtime however, the Graal compiler can initialize the class on build time to improve performance.
  3. Heap snapshotting - The reachable objects are written into the image heap during the build time making the executable faster on startup with the assistance of prepopulated heap space.
The points-to analysis makes objects reachable in the image heap, and the snapshotting that builds the image heap can make new methods reachable for the points-to analysis. Thus, points-to analysis and heap snapshotting are performed iteratively until a fixed point is reached
Native Image Build Illustration
Image source: https://www.infoq.com/articles/native-java-graalvm/
After the analysis is complete, Graal compiles all the reachable code into a platform-specific native executable. That executable is fully functional on its own and doesn't need the JVM to run. As a result, you get a slim and fast native executable version of your Java application: one that performs the exact same functions but contains only the necessary code and its required dependencies.
This brings about a question on memory management, GC invocation and thread scheduling in the GraalVM, which are delegated to SubstrateVM which is responsible for endorsing the GraalVM with the delegated features. 

Spring Native - What?

The static code analysis on the Graal compiler falls short in identifying the reachable reflective APIs, dynamic object proxies, the Java Native Interfaces (JNI), and the program resources (elements accessed reflectively at runtime), on build-time image generation. However, the compiler can be enriched by supplying details via additional configuration which will be used to detect and handle calls to reflections and proxies in the executable.
Spring Native facilitates compiling the Spring applications to native image executables for containerized cloud and serverless deployments that require lightweight modules so as to leverage the other Native Image advantages offered. The additional configuration files for reflection and dynamic proxies are specified via a generated JSON file with help of Spring Native Hints (Spring-based static code analysis) triggers that are fed in series to the native build process of the executable.
The Native Build Tools provide Maven and Gradle plugins to add support for building Java applications into native executables and testing them using Apache Maven.
Spring Boot Native Illustration
Image source: https://spring.io/blog/2021/12/09/new-aot-engine-brings-spring-native-to-the-next-level

How?

Spring Native transforms the source code sent to Native Image. For instance, Spring Native turns the spring.factories service-loader mechanism into static classes that the resulting Spring Native application knows to consult instead. It also transpiles all your Java configuration classes (those annotated with @Configuration) into Spring’s functional configuration, eliminating whole swaths of reflection for your application and its dependencies.

Spring Native also automatically analyzes your code, detects scenarios that will require GraalVM configuration, and furnishes it programmatically. Spring Native ships with hint classes for Spring & Spring Boot and third-party integrations. 

Spring application with the below setup metamorphosis to Spring Native application,
  • Spring Native dependency in pom.xml
<dependency>
    <groupId>org.springframework.experimental</groupId>
    <artifactId>spring-native</artifactId>
    <version>0.12.0</version>
</dependency>
  • Build packs
       <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
        <configuration>
          <classifier>${repackage.classifier}</classifier>
          <image>
            <builder>paketobuildpacks/builder:tiny</builder>
            <env>
              <BP_NATIVE_IMAGE>true</BP_NATIVE_IMAGE>
            </env>
          </image>
        </configuration>
      </plugin>
  • AOT transformation dependency
<dependency>
    <groupId>org.springframework.experimental</groupId>
    <artifactId>spring-aot-maven-plugin</artifactId>
    <version>0.12.1</version>
</dependency>
  • Build plugins
    <plugin>
        <groupId>org.springframework.experimental</groupId>
        <artifactId>spring-aot-maven-plugin</artifactId>
        <version>${spring-native.version}</version>
        <executions>
          <execution>
            <id>test-generate</id>
            <goals>
              <goal>test-generate</goal>
            </goals>
          </execution>
          <execution>
            <id>generate</id>
            <goals>
              <goal>generate</goal>
            </goals>
          </execution>
        </executions>
    </plugin>
  • Profiles
  <profiles>
    <profile>
      <id>native</id>
      <properties>
        <repackage.classifier>exec</repackage.classifier>
        <native-buildtools.version>0.9.13</native-buildtools.version>
      </properties>
      <dependencies>
        <dependency>
          <groupId>org.junit.platform</groupId>
          <artifactId>junit-platform-launcher</artifactId>
          <scope>test</scope>
        </dependency>
      </dependencies>
      <build>
        <plugins>
          <plugin>
            <groupId>org.graalvm.buildtools</groupId>
            <artifactId>native-maven-plugin</artifactId>
            <version>${native-buildtools.version}</version>
            <extensions>true</extensions>
            <executions>
              <execution>
                <id>test-native</id>
                <phase>test</phase>
                <goals>
                  <goal>test</goal>
                </goals>
              </execution>
              <execution>
                <id>build-native</id>
                <phase>package</phase>
                <goals>
                  <goal>build</goal>
                </goals>
              </execution>
            </executions>
          </plugin>
        </plugins>
      </build>
    </profile>
  </profiles>


The aforementioned sequence generates a native platform specific executable binary that can be run with the command,
 mvn -Pnative -DskipTests clean package
./target/<application_name>

mvn spring-boot:build-image - for Spring Boot applications

In Spring application, if there are any classes that uses, reflection, classpath resources, or dynamic proxies, executing the native image by associating it with generated JSON configuration files can be attained by specifying the TypeHints on the class, (providing a Hint to the Native)

@TypeHint(types = ReflectionRunner.Customer.class, access = { TypeAccess.DECLARED_CONSTRUCTORS, TypeAccess.DECLARED_METHODS })

Here, we say that we want reflective access over the constructors and methods of ReflectionRunner.Customer. There are other TypeAccess values for different kinds of reflection.

@ResourceHint(patterns = "Log4j-charsets.properties", isBundle = false)

With the hint specification, we say that we want to load file that is present in the jar file as a dependency on the classpath.

@JdkProxyHint(types = { com.example.extensions.ProxyRunner.Animal.class, org.springframework.aop.SpringProxy.class, org.springframework.aop.framework.Advised.class, org.springframework.core.DecoratingProxy.class })

Proxies create subclasses or implementations of types. Spring knows about two kinds: JDK proxies and AOT proxies. JDK proxies are limited to interfaces with Java’s java.lang.reflect.Proxy. AOT proxies are a Spring-ism, not part of the JRE. These JDK proxies are typically subclasses of a given concrete class and may include interfaces. Native Image needs to know which interfaces and concrete classes your proxy will use.
AOT Architecture Ilustration
Image source: https://spring.io/blog/2021/12/09/new-aot-engine-brings-spring-native-to-the-next-level

In the upcoming post, we shall engage with Graal compilation of Spring Native code with AOT and unravel its sleeves of miracles to lance them!

References

  1. https://spring.io/blog/2021/03/11/announcing-spring-native-beta
  2. https://www.graalvm.org/22.3/reference-manual/native-image/
  3. https://www.graalvm.org/22.2/reference-manual/native-image/guides/build-spring-boot-app-into-native-executable/
  4. https://www.infoq.com/articles/native-java-spring-boot/
  5. https://www.infoq.com/articles/native-java-graalvm/
  6. https://www.vinsguru.com/spring-boot-graalvm-native-image/
  7. https://tanzu.vmware.com/content/slides/running-spring-boot-applications-as-graalvm-native-images
  8. https://piotrminkowski.com/2021/03/05/microservices-on-knative-with-spring-boot-and-graalvm/
  9. https://docs.spring.io/spring-native/docs/current/reference/htmlsingle/
  10. https://github.com/spring-projects-experimental/spring-native/tree/main/samples
  11. https://spring.io/blog/2021/12/09/new-aot-engine-brings-spring-native-to-the-next-level

Disclaimer: 
This is a personal blog. Any views or opinions represented in this blog are personal and belong solely to the blog owner and do not represent those of people, institutions or organizations that the owner may or may not be associated with in professional or personal capacity, unless explicitly stated. Any views or opinions are not intended to malign any religion, ethnic group, club, organization, company, or individual. All content provided on this blog is for informational purposes only. The owner of this blog makes no representations as to the accuracy or completeness of any information on this site or found by following any link on this site. The owner will not be liable for any errors or omissions in this information nor for the availability of this information. The owner will not be liable for any losses, injuries, or damages from the display or use of this information.
Downloadable Files and ImagesAny downloadable file, including but not limited to pdfs, docs, jpegs, pngs, is provided at the user’s own risk. The owner will not be liable for any losses, injuries, or damages resulting from a corrupted or damaged file.
  • Comments are welcome. However, the blog owner reserves the right to edit or delete any comments submitted to this blog without notice due to :
  • Comments deemed to be spam or questionable spam.
  • Comments including profanity.
  • Comments containing language or concepts that could be deemed offensive.
  • Comments containing hate speech, credible threats, or direct attacks on an individual or group.
The blog owner is not responsible for the content in the comments. This blog disclaimer is subject to change at any time.

Comments

Popular posts from this blog

Tech Conversant Weekly Jul 03 - Jul 15

Topic: General                                                                                                                                              Level: All Welcome to the world of cutting-edge technology! Every bi-week, we bring you the latest and most incredible advancements in the tech industry that are sure to leave you feeling inspired and empowered. Stay ahead of the game and be the first to know about the newest innovations shaping our world. Discover new ways to improve your daily life, become more efficient, and enjoy new experiences. This time, we've got some exciting news to share with you! Boosting Java startup with Class Data Sharing (CDS) https://www.youtube.com/watch?v=vvlQv1Dh-HU JDK21 LTS Maintenance and Support https://www.youtube.com/watch?v=3bfR22iv8Pc Health checking of multiple cloud applications with Spring Cloud Gateway https://spring.io/blog/2023/07/05/active-health-check-strategies-with-spring-cloud-gateway Functional Style Non-reactive HTTP clie

Tech Conversant Weekly Jun 19 - Jul 01

Topic: General                                                                                                                                              Level: All Welcome to the world of cutting-edge technology! Every bi-week, we bring you the latest and most incredible advancements in the tech industry that are sure to leave you feeling inspired and empowered. Stay ahead of the game and be the first to know about the newest innovations shaping our world. Discover new ways to improve your daily life, become more efficient, and enjoy new experiences. This time, we've got some exciting news to share with you! Modelling common behaviors between the List and the Set interface has been partially provided by LinkedHashSet. Now from JDK21 with the new interface SequencedCollection extending the Collection interface and is also extended by the List, SortedSet via SequencedSet (for reversal operation), Deque. The SequencedMap interface extends the Map interface by providing the below me

Microservices - Design Patterns

Topic: Software Design                                                                                                        Level: Intermediate Microservices - What? Microservice is a software design methodology, delegated to perform an isolated decoupled single functionality (following the Single-Responsibility Principle from object-oriented SOLID design principles).  Moreover, microservices by design, are decoupled making it easy to develop, test, maintain, deploy, configure, monitor and scale modules independently. Microservices - Why? Having one microservice would not be helpful without it being able to interact with other microservices, to aid in bringing an end-to-end business solution. So arises a question, how can I design a software system that is resilient, decentralized, fault-tolerant, scalable, maintainable, and extensible that complies with the microservice architecture? Design Patterns - What? Design patterns are solutions for commonly occurring problems within a given