Java web servers in JVM runtime or as a native application ?

Martin Grigorov
3 min readOct 25, 2020

Lately with the big progress of GraalVM development many JVM web frameworks started suggesting to compile our applications to native binaries (for Linux, MacOS or Windows).

In this article I am going to explore whether this gives us any advantages.

Recently I’ve shared an article comparing the performance of several HTTP2 web servers. For the purpose of this article I am going to create a native image of the embedded Apache Tomcat application and see whether this is going to improve the results.

The first step is to download and install GraalVM.

There are Linux x86_64 and aarch64 distributions for GraalVM Community Edition but only x86_64 for the Enterprice Edition!

The next step is to install thenative-image tool. There are two ways: 1) download it from GitHub/OTN; 2) or use gu (the GraalVM update tool): gu install native-image

Now we can build the native binary for our application! The application is using Apache Maven as a build tool so we can make use of the Native Image Maven plugin:

This was the easier part!

Issue #1

Trying to build the native binary I’ve found a regression in Apache Tomcat 9.0.39/10.0.0-M9 — org.apache.tomcat.util.descriptor.tld package is no more part of tomcat-embed-core.jar but the GraalVM resources config was not updated and GraalVM was complaining that org.apache.tomcat.util.descriptor.tld.LocalStrings cannot be found. It is fixed for the next versions.

Issue #2

Trying to start Tomcat with NIO2 protocol was failing. GraalVM complained that it cannot find the class (org.apache.coyote.http11.Http11Nio2Protocol). Here the fix was to add this class to the Tomcat’s GraalVM reflection configuration.

Issue #3

Trying to start Tomcat with APR protocol was failing while trying to load libtcnative.so. First GraalVM seems to ignore the special Linux environment variable LD_LIBRARY_PATH, and Tomcat was not able to find the native library. Using -Djava.library.path=/path/to/libtcnative-folder worked! The next error was a little bit cryptic: Unsupported JNI version 0xffffffff ?! Googling didn’t give a direct answer but after a while I’ve found these two pages and I’ve figured that Tomcat’s GraalVM support misses a JNI config. Here is the fix.

Update: the fix has been partially reverted because it caused some problems with applications which use Netty. See https://bz.apache.org/bugzilla/show_bug.cgi?id=64875 for more details.

We are all set! Let’s run the tests!

Using the same Vegeta command as in the previous article:

echo ‘{“method”:”GET”, “url”:”$SCHEME://$TARGET:$PORT/$PATH”}’ | eval vegeta attack -http2 $H2C -format=json -rate 0 -max-workers 128 -insecure -duration 30s | vegeta encode | vegeta report — type json > http2-result.json

we get the following results:

What we see here is:

  • the native images created by GraalVM Community Edition are the slowest
  • the native images created by GraalVM Enterprice Edition give similar throughtput as OpenJDK 15
  • GraalVM (both CE & EE) used as Java Runtime Environment gives better throughput than OpenJDK!

Let’s try to list the pros and cons of GraalVM native image:

Pros:

  • really fast startup time ! — This is nice, but it is not so important for a web server. It would help a lot for a command line application or for a serverless application (lambda functions).
  • less consumed memory — The benefit here is not that big actually. While creating the native image GraalVM will load only the actually used classes but this is a very small part of the used memory by a Java application. The actual consumption is the Heap memory, which is on par with the old fashioned way (java -jar myapp.jar).
  • GraalVM Enterprice Edition provides Profile Guided Optimizations, i.e. one can build an optimized native image by providing profiling data collected in an earlier run of the application.

Cons:

  • As we saw above the results are worse
  • The time to build the application is longer. The bigger the application — the longer. Also it consumes a lot of memory to build the native binary.

[embedded-tomcat-http2:3062329] [total]: 60,844.39 ms, 5.91 GB

  • The size of the binary is big! — in my case :

embedded-tomcat-http2 — 49M

tomcat-embedded-1.0-SNAPSHOT.jar — 3.7M (this is a shaded jar with all dependencies!)

  • Less capable Garbage Collector — Serial GC . With GraalVM Enterprice one can use G1 GC too!

For Java web application I wouldn’t use native image yet, but I’d definitely recommend using GraalVM as a JRE!

--

--