6

Running Spring boot application as Native Image

 2 years ago
source link: https://blog.knoldus.com/running-spring-boot-application-as-native-image/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Running Spring boot application as Native Image

Reading Time: 6 minutes

Hi all. In the last post on GraalVm we saw how polyglot functionality works when we are working with GraalVm. We saw how multiple languages can be tied together in a single application. Moreover, we saw how to pass variables and state from one programming language to another. You can visit the post here: https://blog.knoldus.com/going-polyglot-with-graalvm/. In this blog we will cover the other aspect of using GraalVm: lower Memory footprints and faster startup times. GraalVm uses the concept of native images to suppport a lower memory footprint and very fast startup time for an application. As a result, our focus here is to run a spring-boot application as a normal jar on the Jvm and as a native image on grall. We will capture and the metrics like boost in startup time and etc. so let’s begin.

Prerequisites

So here are some basics you should be aware of before we move ahead:

  • Basic knowledge of a spring boot web application, like how to create and run one.
  • Some basic understanding of GraalVm.
  • Setup of GraalVm done on your local, for that you can follow instructions here: https://blog.knoldus.com/going-polyglot-with-graalvm/
  • Familiarity with maven and Java programming language.

That’s all. Now, let’s analyze what are native images.

Native Image

I believe most of the developers have gone through the pain of longer waiting for their applications to start. Depending upon the size and complexity of your application the startup time can vary. As a result, sometimes applications take more than 30-40 seconds to start and running. Now think how much time it would take if you have to repeat the same process n number of times? 🙁 Yeah, I think you are now getting there. The pain is real. Sitting idle, watching your application load classes and jars everytime during startup is not something anyone would like. Can we reduce this startup time? Can we reduce the memory footprint of our application? Is there a way we can load necessary classes once and run app directly without reloading them on every fresh run?

Well there are many other such questions revolving around this topic and GraalVm is an answer to them. How? Through the native image support of GraalVm. Let’s understand the concept of native image, quoted from the official GraalVm documentation:

"GraalVM Native Image allows you to ahead-of-time compile Java code to a standalone executable, called a native image. This executable includes the application classes, classes from its dependencies, runtime library classes from JDK and statically linked native code from JDK. It does not run on the Java VM, but includes necessary components like memory management and thread scheduling from a different virtual machine, called “Substrate VM”. The resulting program has faster startup time and lower runtime memory overhead compared to a Java VM."

That explains a lot. So, It’s the AOT(Ahead Of Time) compilation instead of the JIT that makes startup time faster and lowers the memory overhead. Overall, native-image is an utility, that processes all the classes of our application along with their dependencies. This utility supports the JVM based languages such as Java, Scala, Kotlin, and etc.

Leveraging Native Image

Since native image is a utility it does not comes by default bundeled with your GraalVm installation. So, if you have already setup GraalVm home as per the instructions shared in the previous blog just use the below command:

gu install native-image

Where gu is a graal utility command provider, and installs other utilities.

It is important to note here, that when we create our spring boot application we need to provide an additional directory under the resources section. This is, META-INF/native-image, and can be seen in the code structure. It is for providing the different configuration options for the native image build process. “Repository link is at the end of the blog, please feel free to clone the repo now if you like otherwise we will see the details in a bit itself.”

Let’s move ahead, to creating a small spring boot application.

Initialize a spring boot application

Well let’s quickly develop a small application that suffice our use case, follow these steps:

  • Go to Spring initializer, and select Maven, Java 1.8 and provide artifact id and a description for your app.
  • Go to the dependency bar and add the following dependencies from the list:
    • spring-boot-starter-web
    • spring-boot-starter-test
  • Click on generate the project, this downloads a Zip file of the project.
  • Extract the project to your desired location and then import in your prefered IDE, I prefer IntelliJ.

Once we have added the basic set of dependencies, we can move ahead to leverage native image specific dependencies. Since, native image support is still experimental from Spring, we need to add few additional dependencies. We list them down here:

    <dependency>
      <groupId>org.springframework.experimental</groupId>
      <artifactId>spring-graal-native</artifactId>
      <version>0.6.0.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context-indexer</artifactId>
    </dependency>

The spring-graal-native dependency brings together several components. It includes the GraalVM feature implementation.

The spring-context-indexer has been in Spring for a while. A native image has no knowledge of the classpath. Thus, it is impossible to explore the classpath to find components at runtime. The indexer actually produces a list of components at Java compile time and captures it in a spring.components file in the built application. If Spring starts and finds this file, it uses it instead of attempting to explore the classpath.

We will also need to add external repositories to our pom.xml for loading graal supported binaries in the app, let’s see:

External Repositories
<repositories>
    <repository>
      <id>spring-milestones</id>
      <name>Spring Milestones</name>
      <url>https://repo.spring.io/milestone</url>
    </repository>
  </repositories>
  <pluginRepositories>
    <pluginRepository>
      <id>spring-milestones</id>
      <name>Spring Milestones</name>
      <url>https://repo.spring.io/milestone</url>
    </pluginRepository>
  </pluginRepositories>

Once we have set the repositories and dependencies we will move to adding the native image maven plugin in our pom. The plugin looks like this:

<profiles>
    <profile>
      <id>graal</id>
      <build>
        <plugins>
          <plugin>
            <groupId>org.graalvm.nativeimage</groupId>
            <artifactId>native-image-maven-plugin</artifactId>
            <version>20.0.0</version>
            <configuration>
              <buildArgs>
                -Dspring.graal.remove-unused-autoconfig=true --no-fallback --allow-incomplete-classpath
                --report-unsupported-elements-at-runtime -H:+ReportExceptionStackTraces --no-server
                -H:IncludeResourceBundles=javax.servlet.http.LocalStrings
                -H:IncludeResourceBundles=javax.servlet.LocalStrings
              </buildArgs>
            </configuration>
            <executions>
              <execution>
                <goals>
                  <goal>native-image</goal>
                </goals>
                <phase>package</phase>
              </execution>
            </executions>
          </plugin>
          <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
          </plugin>
        </plugins>
      </build>
    </profile>
  </profiles>

The native image needs to know the entry point to our application, we define that by including a <start-class></start-class> under the properties of pom.

<start-class>com.knoldus.springbootandgraalvm.SpringBootAndGraalvmApplication</start-class>

That is all we need to add to our spring boot pom for generating graal native images and running it on GraalVm.

Note that in the spring boot application class, the annotation is defined as:

@SpringBootApplication(proxyBeanMethods = false)

We have used `proxyBeanMethods = false`, to make sure that we avoid the use of cglib and any other non-JDK proxies in the app. GraalVm doesn’t likes them at all. 😛

Running the application

Well to run the application we have 3 possible ways:

  1. To run it as a flat jar, a normal JVM based spring-boot application.
  2. Create a native image and then run it on the JVM itself.
  3. To run the native-image on the GraalVm.

We will see all the 3 possible ways one by one and will also compare the metrics related to them. Let’s start.

Running application on the JVM

To run as a flat jar on the JVM we just need to run the below mentioned command in the terminal:

mvn clean spring-boot:run

or we can go to springBootApplication class right click on the main method and select run as Java application.

This will start initializing the service and will run the service on port 8080. We can look at the service logs the amount of time it took to compile and start the service, could vary on different machines.

Running Native-image on the JVM

To run the native image we must first create it. Please note that native image creation is a heavy process and may lead to some issues in system with less than 16G of RAM. Though, once the image is created running it is quite straight and simple.

Follow the following steps in order to create and run the native image on the JVM.

1. mvn clean package
2. export META=src/main/resources/META-INF
3. mkdir -p $META 
4. java -agentlib:native-image-agent=config-output-dir=${META}/native-image -jar target/spring-boot-and-graalvm-0.0.1-SNAPSHOT.jar

That is all your application would start running at the port 8080 and we can test that by calling the endpoint: localhost:8080/users/admin. However we should be more interested in the logs that application generated. This time you can see that the server took quite less time to start as compared to the JVM flat jar run.

Native image on the GraalVm

Well coming to the last approach, here we will use the graal maven plugin for native image generation and then will run the image. To run the application we need to do the following:

1. Package your native image with native image maven plugin:
mvn -Pgraal clean package
This builds the native image, is a lengthy and heavy process so be patient.

2. Run the native image. 
Go to the terminal and execute:
./target/com.knoldus.springbootandgraalvm.springbootandgraalvmapplication

You will notice that as soon as you entered the 2nd command the service has started running and you can check that on port 8080. Rest of the things remain the same though this time the service starts in a flash. Consequently, application logs show the huge difference in startup time with this approach and the others. You can see a >60 % fast startup time now as compared to other methods.

That’s the benefit of running your native image over graalVm.

I hope you liked it.

Conclusion

We have seen the benefits and performance benchmarks when run our spring boot application as a native image on the graalVm. The metrics might differ on different machines but the performance gained is surely something that would excite all. All the code is present in the repo here. Please feel free to clone it and go through the application as we proceed in the blog. In case you have any suggestions or queries please feel free to post them in the comments section below. Until next time, bbye. 🙂

References


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK