Faster Maven builds with MAVEN_OPTS

John Freeman
4 min readApr 29, 2019

--

I compared the effect of a number of JVM options on Apache Maven build durations of the Apache Commons Collections library.

Here’s what I learned (at least for this project on my laptop):

  • Builds with Java 8 are faster than Java 11 (~20%)
  • HotSpot builds are faster with tuning (~14%)
  • OpenJ9 builds are much faster with tuning (~51%)
  • Once tuned OpenJ9 builds were slightly faster than HotSpot, but it was close so your mileage may vary.
  • I’d recommend the following MAVEN_OPTS for HotSpot:
    -Xshare:on -XX:TieredStopAtLevel=1 -XX:+UseParallelGC
  • I’d recommend the following MAVEN_OPTS for OpenJ9:
    -Xquickstart -Xshareclasses:name=mvn -DargLine=-Xquickstart
  • For local development you can also try adding -Xverify:none, it may give a slight performance boost (don’t use this on your CI server or in production).

What is MAVEN_OPTS

MAVEN_OPTS is an environment variable used by the shell scripts that launch the Java Virtual Machine (JVM) that runs Maven. Using MAVEN_OPTS you can pass options to the JVM when it’s launched.

Tuning HotSpot for Maven builds

baseline: -Xshare:off

-Xshare:off is actually the default for Java 8, but I specified it explicitly for consistency with the Java 11 test (where the default is -Xshare:autobut the shared archive isn’t created by default).

throughput gc: -Xshare:off -XX:+UseParallelGC

Use the garbage collector optimized for throughput. -XX:+UseParallelGC is also the default for Java 8, again this is specified explicitly for consistency with the Java 11 test.

CDS: -Xshare:on

Enable class data sharing. In theory, this should improve startup performance and save on RAM. For these builds, the performance impact appears to be negligible, but enabling it may save a bit of RAM when forking for the unit tests.

Note: you need to run the following to create the shared archive before class data sharing will work:

java -Xshare:dump

C1 only: -Xshare:off -XX:TieredStopAtLevel=1

The HotSpot JIT compiler is split into two tiers the C1 compiler (fast, lightly optimizing) and the C2 compiler (slower, highly optimizing). Stopping at the C1 compiler clearly gives a big boost to Maven builds. The additional profiling and compilation costs of enabling the C2 compiler significantly outweigh the performance benefits of using the highly optimized code.

no verify: -Xshare:off -Xverify:none

Never use this to disable bytecode verification in production (or even on your CI server), but it may make your local builds a tiny bit faster. It’s possible it may provide a bigger improvement on a project with more dependencies.

tuned: -Xshare:on -XX:TieredStopAtLevel=1 -XX:+UseParallelGC -Xverify:none

Class data sharing (for the RAM saving) and C1 only + no verify (for performance).

The main difference with Java 11 configuration is the default garbage collector changed to the G1 Garbage Collector. Changing back to the Parallel Garbage Collector gives better throughput. The only downside is you may have to specify -XX:MaxPermSize with the Parallel Garbage Collector.

Tuning OpenJ9 for Maven builds

baseline

The out of the box performance of the JVM.

throughput gc: -Xgcpolicy:optthrouput

Use the garbage collection policy optimized for throughput.

class cache: -Xshareclasses:name=mvn -DargLine=-Xshareclasses:none

This is a cache for the JIT compiler so it doesn’t have to recompile classes to native-code. This provides the single biggest performance improvement for OpenJ9. Unfortunately, it’s incompatible with JaCoCo so you have to disable it when forking the JVM to run the unit test by passing an additional argument -DargLine=-Xshareclasses:none to the Maven Surefire Plugin.

quick start: -Xquickstart

Disables the JIT compiler’s more-costly native-code optimizations. This also provides a big performance improvement.

no verify: -Xshare:off -Xverify:none

Never use this to disable bytecode verification in production (or even on your CI server). Provides a small performance improvement.

tuned: -Xquickstart -Xshareclasses:name=mvn -DargLine=-Xquickstart -Xverify:none

All the features that improved performance.

The main difference with Java 11 was using the throughput garbage collection policy made performance a little worse.

Test methodology

You can find the scripts I used for the benchmarks and other details in this GitHub project.

The elephant in the room

Ok, so if quick builds are what’s most important to you, you’re almost certainly better off using Gradle instead of Maven. That said, Maven has its strengths, and if you decide to use it, you may as well squeeze as much speed out of it as possible.

Due to its different execution model, you shouldn’t assume JVM options that make Maven faster will have the same effect on Gradle.

--

--