JVM Startup Time and Memory Footprint Enhancements

Thiago Mendes
The Startup
Published in
7 min readMay 10, 2020

In this post, I would like to talk about some recent updates on the Java platform that can help you optimize your application’s startup time and improve the memory footprint.

Nowadays, due to architectural changes, with containerized environments and serverless applications, it is increasingly important to be concerned with the startup time and resource consumption of your application, something that was not a major concern until a few years ago.

First of all, I would like to make it clear that this post aims to help achieve these optimizations with native JVM resources. I know that there are other strategies with native compilation and promising projects like GraalVM and Quarkus, but I believe that it is not yet time to use them in production environments. We can talk more about these projects in specific posts.

For this post, I would like to bring three JDK Enhancement Proposals (JEP) that have made improvements to the JVM’s Class-Data Sharing process and can help us achieve these optimizations.

But before I start, I would like to give a brief introduction to Class-Data Sharing, also known as CDS.

In summary, whenever a Java application is started, the JVM performs a pre-analysis of the classes and loads the metadata for these classes into an area called metaspace. This consumes time and memory, consequently, it directly influences the startup time and resource consumption of your application.

The metaspace is a JVM area that is part of the non-Java-heap. As of version 8 of Java, Metaspace replaces another area of ​​the JVM known as PermGen. I will not go into detail on the JVM memory model in this post.

In version 5 of the JDK (Java Development Kit), Class-Data Sharing was introduced to make the metadata of the pre-loaded classes available in a shared file, which could be shared in multiple instances of the JVM. This brought benefits to the memory footprint and reduced the startup time, however, until then, this occurred only with the JVM bootstrap classes (rt.jar and other libraries present in the JRE’s lib directory).

With the arrival of version 10 of Java, a feature called Application Class-Data Sharing (JEP 310) was introduced, which is an extension of the CDS and aims to allow pre-loading of metadata files, but now, in addition to the bootstrap classes, it also loads the other JDK and application classes.

How to use Application Class-Data Sharing?

Let’s start by talking about what was introduced in JEP 310, in version 10 of Java. With the addition of this feature, we can generate the metadata file with the JDK classes and also with the application classes. The JVM can statically use this file during its startup process, thus avoiding all pre-processing of these classes again.

To generate the file of classes related to JDK, just execute the command:

java -Xshare:dump

This will generate a file called classes.jsa in the JRE’s /lib/server/ directory.

To use this file that was generated, just run your application with the following parameter:

java -Xshare:on -jar your-app.jar

You can also enable your JVM’s class loader log to see which classes were loaded from the shared file and which were not.

java -Xlog:class+load:file=jvm-cds.log -jar your-app.jar

It is important to note that not all classes can be loaded from the shared file, this depends on several conditions that are analyzed by the JVM. Classes that are loaded from the shared file will appear in the log file as:

... source: shared objects file

At the end of this post, I will be doing some labs to measure time savings and memory consumption, so I will not do that now. Anyway, feel free to go ahead and run the tests while advancing with the commands presented in this post.

Now let’s see how in addition to loading the JDK classes, we can also load the application classes into the file.

First of all, you need to use the command below to generate the file with the class metadata for your application.

java -XX:DumpLoadedClassList=app-classes.txt -jar your-app.jar

At this point, your application will be executed. You can run your application for a while to map other classes that will be loaded at run time or simply end the process to obtain the mapping of classes used during startup. Upon terminating the process, a file called app-classes.txt will be generated in the specified directory. In the case of the example, it will be generated in the current directory.

But this is not yet the file understood by the JVM, we need to convert the file app-classes.txt to a file with the class metadata that can be understood by the JVM.

For this, we will use the command below.

java -Xshare:dump -XX:SharedClassListFile=app-classes.txt -XX:SharedArchiveFile=app-classes.jsa --class-path your-app.jar

Note that with the above command, we generate the app-classes.jsa file from the app-classes.txt file, this is the type of file understood by the JVM.

With the generated app-classes.jsa file, it’s time to use it when the application is loaded. Just as we did when loading the JDK class file, we can use the logs command (-Xlog:class+load: file=jvm-cds.log) to see which classes were loaded from the shared file.

java -XX:SharedArchiveFile=app-classes.jsa -jar your-app.jar

Right now, you will be enjoying all the benefits of JEP 310, loading both the JDK classes and the application classes through a static file. Until your application undergoes new changes, you can continue using this same file during the initialization of the JVM.

At this point, it would be a great time to run new tests and validate the results, but as I said, I will leave that to the end of the post.

Speaking now of the improvement we had in Application Class-Data Sharing in version 12 of Java (JEP 341), where files related to JDK classes are generated and loaded automatically, without the need for the -Xshare:dump command. If you want to disable this, you can use the command below.

-Xshare:off

In this way, if you are using Java version 12 or higher, you no longer have to worry about the CDS related to the JDK classes.

Now we will talk about another major improvement related to CDS (JEP 350), however in version 13 of Java. This improvement allows the file with the metadata of the application classes (app-classes.jsa), to be generated automatically at the end of the process execution, without the need to generate the app-classes.txt file and convert it to app-classes. jsa.

To do this, just use the command below.

java -XX:ArchiveClassesAtExit=app-classes.jsa -jar your-app.jar

With the generated app-classes.jsa file, just use the command below to load it.

java -XX:SharedArchiveFile=app-classes.jsa -jar your-app.jar

Another important improvement that we have in using this Application Class-Data Sharing strategy in the application classes, is related to a known problem in Java which is the slowness during the first load of some applications, due to some classes that are loaded only in runtime. During our tests, this will be visible through the first load of Spring’s dispacherServlet.

Well, it’s time to test this.

For that, I will use an application based on Spring Boot with an H2 database in memory.

I will use Java version 13 (Oracle HotSpot) and test the application startup time, memory footprint, and response time of an endpoint during the first load.

The first load test will be triggered through an endpoint that will insert some data into H2.

To get the data related to the memory footprint in the JVM, I will use the VisualVM tool.

Let’s start with the startup time test.

Startup time without active Application Class-Data Sharing:

Without the Application Class-Data Sharing active, the startup time was 3.84 seconds.

Startup time with active Application Class-Data Sharing:

First of all, we need to generate the metadata file for the application classes, through the command:

java -XX:ArchiveClassesAtExit=app-classes.jsa -jar your-app.jar

With the generated file, we will run the application with the Application Class-Data Sharing active.

With the Application Class-Data Sharing active, the startup time was 2.91 seconds.

Result: We had a gain of more than 30% in the startup time with the Application Class-Data Sharing active.

Memory consumption without active Application Class-Data Sharing:

The memory consumption in the metaspace area without the active Application Class-Data Sharing was 67MB.

Memory consumption with active Application Class-Data Sharing:

The memory consumption in the metaspace area with the active Application Class-Data Sharing was 23MB.

Result: We had a reduction of more than 190% in memory consumption in the metaspace area of the JVM.

First load test without active Application Class-Data Sharing:

The response time of the first load without active Application Class-Data Sharing was 204ms.

First load test with active Application Class-Data Sharing:

The response time of the first load with active Application Class-Data Sharing was 165ms.

Result: We had a 23% gain in response time during the first load test.

Conclusion

Even without bringing the gains promised by projects that use the execution of native codes outside the JVM, I believe it is a great alternative for optimizing startup time and memory footprint. However, for you to enjoy these benefits, you must be running your applications on Java 10 or higher. I particularly advise using LTS versions of the JVM, with version 11 being the most recent.

--

--