Creating your own runtime using jlink

With the release of the latest LTS version of Temurin we have decided not to ship Java Runtime Environments (JREs) separately from the JDK downloads. This post will show you how to create your own runtime for Java 17+ which is comparable to a legacy JRE if required.

Why have you decided to stop shipping JREs?

NOTE: This paragraph has been superceded since we are now shipping JREs with 17+ again - see https://adoptium.net/blog/2021/12/eclipse-temurin-jres-are-back/ for the details, however we still recommend using jlink to produce your own cut down java runtimes where possible

While the OpenJDK build process still has support for building a JRE via the legacy-jre target it is, as the name suggests, legacy functionality. The new LTS version provided us with an opportunity to make a clean break and we have decided now is the time to no longer provide JREs since there are more efficient options available. It also simplifies the list of downloads we have available and reduces our testing overhead. We will continue to produce them for the existing 8 and 11 versions to retain continuity for those using them. The discussion on this happened in this issue.

The good news is that it is very easy to produce your own runtime that looks comparable to a legacy JRE, and in many cases it can be smaller than an old legacy JRE would be!

Sounds good. So how do I create my own “JRE”?

Creating your own runtime that is comparable to a legacy JRE is simpler then you might think. Firstly, download and extract the JDK archive. Second, use jlink to create your own runtime that will be smaller, yet still provide equivalent functionality to the legacy JRE. Replace jdk-17+35 in the examples below with the version of Java you are working with, and replace the forward slashes with the path separator on your platform (e.g. \ for Windows):

   jdk-17+35/bin/jlink --add-modules ALL-MODULE-PATH --output jdk-17+35-jre \
      --strip-debug --no-man-pages --no-header-files --compress=2

Once you have done that, the contents of the jdk-17+35-jre will work in place of a legacy JRE and be significantly smaller expanded on disk than either the JDK or legacy JRE would be.

What are the disk space savings?

It will vary by platform and the version of Java you are using, but using one example with the jdk-17+35 release, the full JDK for one platform is about 312Mb on disk. The jlinked runtime using the above command is about 95Mb.

Is this identical to the legacy JRE?

Not quite. By default there are some extra modules included via the ALL-MODULE-PATH list than would be included in the legacy JRE. You can list the ones which your runtime supports with java --list-modules. At the time of writing, using the jdk-17+35 release, the list of extra modules is as follows:

 jdk.attach
 jdk.compiler
 jdk.editpad
 jdk.hotspot.agent
 jdk.internal.ed
 jdk.internal.jvmstat
 jdk.internal.le
 jdk.internal.opt
 jdk.jartool
 jdk.javadoc
 jdk.jcmd
 jdk.jconsole
 jdk.jdeps
 jdk.jdi
 jdk.jlink
 jdk.jpackage
 jdk.jshell
 jdk.jstatd
 jdk.random
 jdk.unsupported.desktop

Also, to be able to support some of those modules, you will still have tools like javac, jlink and others in the runtime. The --add-modules command accepts a comma seperated list of modules instead of the full list implied by ALL-MODULE-PATH. You can use a comma-separated set of modules from the --list-modules output to limit it further. As an example you could list all of the modules other than the ones in the list above. Doing so will reduce the size further, to around 66Mb, saving an extra 29Mb.

As of jdk-17+35 the full set of modules needed to do this is shown in the following command:

jdk-17+35/bin/jlink --add-modules java.base,java.compiler,java.datatransfer,java.desktop,java.instrument,java.logging,java.management,java.management.rmi,java.naming,java.net.http,java.prefs,java.rmi,java.scripting,java.se,java.security.jgss,java.security.sasl,java.smartcardio,java.sql,java.sql.rowset,java.transaction.xa,java.xml,java.xml.crypto,jdk.accessibility,jdk.charsets,jdk.crypto.cryptoki,jdk.crypto.ec,jdk.dynalink,jdk.httpserver,jdk.incubator.foreign,jdk.incubator.vector,jdk.internal.vm.ci,jdk.internal.vm.compiler,jdk.internal.vm.compiler.management,jdk.jdwp.agent,jdk.jfr,jdk.jsobject,jdk.localedata,jdk.management,jdk.management.agent,jdk.management.jfr,jdk.naming.dns,jdk.naming.rmi,jdk.net,jdk.nio.mapmode,jdk.sctp,jdk.security.auth,jdk.security.jgss,jdk.unsupported,jdk.xml.dom,jdk.zipfs --output jre-17+35 --strip-debug --no-man-pages --no-header-files --compress=2

Can I make my runtime even smaller?

As mentioned in the introduction, yes you can! You just need to identify which modules your application requires. You can do this with the jdeps command. For example if I take a simple “Hello World!” application I can get the list of modules it requires as follows:

$ jdk-17+35/bin/jdeps Hello.class
Hello.class -> java.base
   <unnamed>        -> java.io        java.base
   <unnamed>        -> java.lang      java.base

This first line shows that my simple application only requires the java.base module. In a more complicated example there will be several of these present in the output. The rest of the output is a breakdown of what packages my application uses and which modules they relate to. I can therefore create an even smaller custom runtime that will be suitable for my application using the following jlink command:

   jdk-17+35/bin/jlink --add-modules java.base --output jdk-17+35-minimaljre \
      --strip-debug --no-man-pages --no-header-files --compress=2

And the new runtime, which comes in at only 32Mb, will still be able to run my “Hello World” application. From this you can see that using jlink you can generate custom runtimes for your application which are even smaller than any legacy JRE we could ship to you.

The jdeps command can also be given a jar file instead of a class and show you all of the modules your packaged application requires.

NOTE: In some cases the cryptography modules may not get picked up by jdeps. If your application uses encryption and you hit problems you may need to add jdk.crypto.ec to the list of modules that you include.

I love this! Does it work for all Java versions?

The jlink command was introduced when the module system was introduced in OpenJDK. For this reason it is not available in Java 8, but is in all later supported versions.

temurin

Do you have questions or want to discuss this post? Hit us up on the Adoptium Slack workspace!


Stewart X Addison

Posted by Stewart X AddisonAdoptium PMC and steering WG member working for Red Hat