How to create a Custom JRE using jlink
Environment
- OpenJDK 11+
Issue
- How to create a Custom JRE in JDK 11?
- How to use using
jlink? - How to create a Custom JRE in JDK 11 using
jlink?
Resolution
Red Hat supports the tool jlink, however, it does not support the resulting JRE. A custom JRE is considered a 3rd party JDK (unsupported)
As explained in the solution Major differences of OpenJDK 11 and OpenJDK 8, the concept of a static JRE has been replaced with modules. The jlink tool is provided to create a custom runtime image that includes only specified modules.
For best results, create the custom JRE from a JDK distribution that is best aligned with the deployment environment: rpm, container, or portable build. For example, you would want to use an rpm or container if your deployment leverages RHEL integration points: crypto polices (e.g FIPS), timezone information, global cacerts.
The steps below assume the Module, also known as Java Platform Module System (JPMS), is already created. In the event it is not, the first step is to create a module is described in Creating a module section.
Creating a Custom JRE
Jlink has the following options:
jlink [options] –module-path modulepath
–add-modules module [, module…]
--output <target-directory>
Given that the module is already created, to create a custom JRE for a module-based application, executing the following command:
$JDK11/bin/jlink --module-path $JDK11/bin/jmods:out --add-modules [your_module] --output [custom_jre_name]
The example below uses the module jlinkModule and will generate the output customjre:
$JDK11/bin/jlink -v --module-path $JDK11/bin/jmods:out --add-modules jlinkModule --output customjre
In other words, this means I would like to run jlink in the jdos:out adding the module jlinkModule and the output will be called customjre. The -v option is for verbosity. This will output the customjre with the structure below and the size is very small: only 59 Mb:
customjre
├── bin
│ ├── java
│ └── keytool
├── conf
│ ├── logging.properties
│ ├── net.properties
│ └── security
│ ├── java.policy
│ ├── java.security
│ └── policy
│ ├── limited
│ │ ├── default_local.policy
│ │ ├── default_US_export.policy
│ │ └── exempt_local.policy
│ ├── README.txt
│ └── unlimited
│ ├── default_local.policy
│ └── default_US_export.policy
├── include
│ ├── classfile_constants.h
│ ├── jni.h
│ ├── jvmticmlr.h
│ ├── jvmti.h
│ └── linux
│ └── jni_md.h
├── legal (...)
├── lib
│ ├── classlist
│ ├── jexec
│ ├── jli
│ │ └── libjli.so
│ ├── jrt-fs.jar
│ ├── jvm.cfg
│ ├── libjava.so
│ ├── libjimage.so
│ ├── libjsig.so
│ ├── libnet.so
│ ├── libnio.so
│ ├── libverify.so
│ ├── libzip.so
│ ├── modules
│ ├── security
│ │ ├── blacklisted.certs
│ │ ├── cacerts
│ │ ├── default.policy
│ │ └── public_suffix_list.dat
│ ├── server
│ │ ├── libjsig.so
│ │ ├── libjvm.so
│ │ └── Xusage.txt
│ └── tzdb.dat
└── release
23 directories, 56 files
Running module from the Custom JRE
To run the module from the new custom JRE:
jlinkModule]$ cd customjre/bin/
bin$ ./java --module jlinkModule/com.example.jlink.HelloRedHat <-- the module is already there, we just need to specify it.
Sep 03, 2020 6:54:03 PM com.example.jlink.HelloRedHat main
INFO: Hello RedHat!
Creating a Custom JRE with a launcher
To create a custom JRE with a launcher: just need to add --launcher. The example below is done with using the name customjrelauncher and the value for the module jlinkModule/com.example.jlink.HelloRedHat.
[jlinkModule]$ $JDK11/bin/jlink -v --launcher customjrelauncher=jlinkModule/com.example.jlink.HelloRedHat --module-path $JDK11/bin/jlink/bin/jmods:out --add-modules jlinkModule --output customjre
java.base file:///$JDK11/bin/jmods/java.base.jmod
java.logging file:///$JDK11/bin/jmods/java.logging.jmod
jlinkModule file:///jlinkModule/out/
Providers:
java.base provides java.nio.file.spi.FileSystemProvider used by java.base
java.logging provides jdk.internal.logger.DefaultLoggerFinder used by java.base
The launcher will be inside the bin directory:
[jlinkModule]$ cd customjre/bin/
$ ls
customjrelauncher java keytool <-- it is inside the bin directory
[bin]$ ./customjrelauncher <-- just execute the custom launcher and the result will be there
Sep 03, 2020 7:21:43 PM com.example.jlink.HelloRedHat main
INFO: Hello World!
Creating a module
Java 9 introduced a new level of abstraction above packages, formally known as the Java Platform Module System (JPMS), or Modules for short. JDK 11 uses this concept as a step to create custom JREs.
The steps to create a module are described below:
-
Create a java file - import all the necessary packages. The java file must be packaged.
-
Create a
module-info.javafile, which will contain a name and the dependencies,systemor not, underrequires:// The module name module jlinkModule { // The required dependency, which is a system module requires java.logging; } -
Compile the
module-info.javaand the class using--module-pathdirectives -
The following structure is an example:
[jlinkModule]$ tree
├── com
│ └── example
│ └── jlink
│ └── HelloRedHat.java <-- that's the java file, with package com.example.jlink;
├── module-info.java <-- that's the module with the modules one need
└── out
├── com
│ └── example
│ └── jlink
│ └── HelloRedHat.class <-- compilated
└── module-info.class <-- compilated
The use of -classpath, -cp, flag is necessary to add custom modules to the runtime. For more information see link: Content from docs.oracle.com is not included.Removed Extension Mechanism.
Example
The module-info.java is attached as an example, as well as the Hello World java file.
Root Cause
This solution explains that one can use the jlink to create custom JRE by pruning the default JDK.
Moreover, the jimage is basically what contains all the linked modules for modularized runtime, which are produced by jlink. So some native occupancy is expected by jimage/jlink.
If this is a problem, set -XX:MaxDirectMemorySize to trim it down, at the cost of more aggressive GC.
About jlink+FIPS
For using FIPS, besides the host having FIPS the adequate FIPS module must be added on jlink.
Use module Content from download.java.net is not included.jdk.crypto.cryptoki,
that has the SunPKCS11-NSS-FIPS module that implements it.
If the user wants to use Bouncy Castle, then our FIPS mode has to be disabled.
| Module | Porpuse |
|---|---|
| Content from download.java.net is not included.jdk.crypto.cryptoki | SunPKCS11-NSS-FIPS |
| java.base | PKCS12 |
Diagnostic Steps
- Unnamed package is not allowed in module:
$ java --module-path out HelloWorld.java
Error occurred during initialization of boot layer
java.lang.module.FindException: Error reading module: out
Caused by: java.lang.module.InvalidModuleDescriptorException: HelloWorld.class found in top-level directory (unnamed package not allowed in module)
- To list the dependent modules:
$ jdeps --module-path out -s --module jlinkModule - For version verification:
$ jlink --version 17.0.8
FIPS
- OpenJDK can be configured to print relevant FIPS mode information at startup by setting the
-Djava.security.debug=propertiesor-Djava.security.debug=all. - When FIPS is enabled:
properties: com.redhat.fips unset, using default value of true
properties: Calling getSystemFIPSEnabled (libsystemconf)...
properties: getSystemFIPSEnabled: reading /proc/sys/crypto/fips_enabled
properties: getSystemFIPSEnabled: read character is '0'
properties: Call to getSystemFIPSEnabled (libsystemconf) returned: false
properties: FIPS mode not detected
properties: FIPS mode support disabled.
- See more information in Running OpenJDK in FIPS mode on RHEL.
This solution is part of Red Hat’s fast-track publication program, providing a huge library of solutions that Red Hat engineers have created while supporting our customers. To give you the knowledge you need the instant it becomes available, these articles may be presented in a raw and unedited form.