*Really* Small Java Apps :: Mar 4, 2019

Since bundling apps and runtime is the new best-practice (whether with Docker or jpackage), and the full JVM weighs in at hundreds of megabytes, how can we include only the minimal JVM subset our application requires? This post explores 4 complementary ways:

  1. Build a minimal JDK image
  2. Use jlink to create an application image
  3. Choose a minimal docker image
  4. Use the jlink image with jpackage

Building the JDK

OpenJDK has many features, including 7 Garbage Collectors (Epsilon, Serial, Parallel, CMS, G1, Shennandoah, ZGC!!) and experimental compilers like Graal. Building the OpenJDK is suprisingly easy (took 12 minutes for a fresh build on my 2015 macbook), so lets strip out what we don’t need!

We care about two configure options: --with-jvm-variants=<variant>[,<variant>...] and --with-jvm-features=<feature>[,<feature>...]. By default configure uses the server variant, which includes features:

The other varients are server, client, minimal, core, zero, and custom. minimal comes with compiler1, dtrace, and serialgc.

My app doesn’t need most of this, so I build a minimal JDK with C1, C2, DTrace, G1GC, and CDS (for quicker startup, requires G1GC):

bash configure --with-jvm-variants=minimal \
    --with-jvm-features=compiler2,g1gc,cds

Configure should spit out something like

====================================================
The existing configuration has been successfully updated in
/Users/august/Documents/prog/openjdk/jdk/build/macosx-x86_64-minimal-release
using configure arguments '--with-jvm-variants=minimal --with-jvm-features=compiler2,g1gc,cds'.

Configuration summary:
* Debug level:    release
* HS debug level: product
* JVM variants:   minimal
* JVM features:   minimal: 'cds compiler1 compiler2 dtrace g1gc minimal serialgc'
* OpenJDK target: OS: macosx, CPU architecture: x86, address length: 64
* Version string: 13-internal+0-adhoc.august.jdk (13-internal)

Tools summary:
* Boot JDK:       openjdk version "13-ea" 2019-09-17 OpenJDK Runtime Environment (build 13-ea+6) OpenJDK 64-Bit Server VM (build 13-ea+6, mixed mode, sharing)  (at /Library/Java/JavaVirtualMachines/jdk-13.jdk/Contents/Home)
* Toolchain:      clang (clang/LLVM from Xcode 10.1)
* C Compiler:     Version 10.0.0 (at /usr/bin/clang)
* C++ Compiler:   Version 10.0.0 (at /usr/bin/clang++)

Build performance summary:
* Cores to use:   4
* Memory limit:   8192 MB

Then make:

make images CONF=macosx-x86_64-minimal-release

You can now use java, jlink, etc from the build output.

If your app uses the Java Module System, it’s easy to make a minimal JRE image, with just the modules you require:

jlink --compress=1 --no-header-files            \
    --no-man-pages --strip-debug                \
    --output <path> --module-path <module path> \
    --add-modules <module>[,<module>...]

Make sure not to use compress=2, since your applicationn will be slower, AND larger (at least for now).

Docker

Google’s Distroless project has minimal docker images. If using a jlink image then the base image (8mb) should be prefered.

Jpackage

There are early-access builds of jpackage available. Jlinked images can be specified with an option.

Conclusions

For a simple project requiring java.net.http, using these steps produced a 23MB jlink image. Hopefully this can be improved further if jlink improves –compress=2 to use LZ4 (https://twitter.com/shipilev/status/1100396679285665794)