Native javac built with GraalVM
In my previous article, I showed you how you can make Apache Maven builds faster by tuning the JVM using
MAVEN_OPTS . In this article, we’re looking at if we can make builds faster by compiling
javac into a native executable using GraalVM
The chart above is from invoking
javac on the main sources of Apache Commons Collections. The native
javac executable reduced compile times by between 37% to 87% relative to the non-native versions. As you can see tuning the JVM makes a big difference, but the native
javac executable is still much faster.
Compiling native javac
You can’t compile
javac directly with
javac needs the system properties that point to where it can find the JAR files that make up the JDK. The following class adds the missing properties:
Note: for readability, this is actually a simplified version of the class used in the benchmark.
The following script will build the native executable:
Note: to squeeze a bit more performance, and minimize binary size, I added two additional command line flags when building
javac-native for the benchmark.
-H:-MultiThreaded this removes unused support for multi-threading.
-H:+NativeArchitecture this compiles using the features of the local CPU.
Be aware this native
javac doesn’t support annotation processors such as Project Lombok. If required, you could probably include the annotation processors when building the native executable.
Using native javac with Maven
javac-native to be under a directory in the
PATH and to have set the
GRAAL_HOME environment variable to be the root of the GraalVM SDK.
You need to configure the maven-compiler-plugin to fork and execute
javac-native, the easiest way to do this is in your Maven
Maven build times
Since few people use
javac directly let’s see what effect this has on Maven build times:
javac executable reduced build time by between 5% to 16%.
Given the unit tests consume most of the build time lets see what the results look like when we skip the test execution:
Without the tests, the native
javac executable reduced build times by between 12% to 39%.
As you can see the native
javac executable significantly improves build times across the board.
Here are the
MAVEN_OPTSfor the tuned builds.
You can find the scripts I used for the benchmarks and other details in this GitHub project.
The adventurous of you are probably wondering if you can use this to make your existing local or CI builds faster. I think executables built with GraalVM
native-image have the potential to change the way we build Java applications in a much more fundamental way.
Java build tools (e.g. Maven and Gradle) are complicated and opinionated beasts. Most build tools for other languages are relatively simple pipelines for invoking a series of native executables. Java build tools are designed to avoid forking new JVMs as much as possible (due to the high performance and RAM cost). This means each Java build tool has to deal with complicated class loading issues and requires a plugin API. It also means most tools invoked during a Java build end up having to write a command line API, a Maven plugin and a Gradle plugin.
Executables built with GraalVM
native-image open the door to making Java build tools simpler and faster. Whether such a tool could match the speed of a warmed up Gradle build server is a question that would need answering, but it’d clearly be faster than Maven.