Java 9 introduced the idea of modular runtime images with Project Jigsaw and officially solved the problem of shipping Java runtime as monolithic artifacts. Monolithic runtime images were not only heavy on memory usage but also slackened overall performance of the application running on it. We now can pack a customized subset of JRE, based on the individual needs of the applications that run on it and increase their efficiency manifold. This article takes on a holistic look at this feature of custom runtime images introduced with Java 9.
An Overview
The impact of the idea of creating a modular runtime image with Java 9 is huge. It opens the door for a custom built application right from the platform on which it runs. The Java platform has increasing its functionality with each new version. It is not a surprise that at some point the runtime will be a monolithic artifact and take a heavy toll on memory and performance. Due to this, developers long been requesting a way out of this problem. Also, most programs do not use Java Platform in its entirety. If a program can be optimized for performance and memory usages, why can’t the platform on which it runs be customized as well? Java 8 took the initial step and tried to implement some aspect of it with Compact Profiles. Java 9 took it forward and implemented a way to custom make runtime images with no constraints that Compact Profiles imposed. It took a holistic approach in packaging runtime images. The platform itself is modularized to enable this feature. Application code packaged in modules can be shipped with custom runtime images that only contain those platform modules which are used by the application. Thus, an application program can be a single bundled artifact which includes the custom JRE. This certainly leverages performance, granting a startup time with fewer memory footprints. If the application runs in the Cloud, these decrease the network overload and download time considerably.
Compact Profile
Although Java 9 got past the concept of Compact Profile introduced with Java 8, it is often helpful to understand and appreciate the milestone reached. In a way, Java 9 fed on the idea and upgraded the concept of a Compact Profile in a more holistic manner.
A Compact Profile defines subsets of the Java SE platform API that can reduce the static size of the Java runtime. This idea is basically targeted to work on resource-constrained devices which have smaller storage capacities, such as an embedded device. There are basically three profiles, called compact1, compact2, and compact3. Each higher numbered profile is a super-set of its lowered numbered profile. This means that compact1 is a proper subset of compact2, compact2 is a proper subset of compact3, and compact3, in turn, is a proper subset of the full stack Java 8 SE API.
References:
- Compact Profiles, Java SE 8 Documentation
- Java SE Embedded 8 Compact Profiles Overview
- An Introduction to Java 8 Compact Profiles
Introducing JIMAGE
The JIMAGE is a special file format introduced with Java 9 to store custom runtime images. This file format is optimized for performance and storage. The file format basically acts as a container for JDK resources, classes, and modules, and indexes them for quick search and faster class loading. Unlike other file formats such as JARs and JMODs, JIMAGE is rarely used by developer as it relates to JDK internals except when one wants to build a custom runtime image. Class loading is faster with JIMAGE then JARs or JMODs as it is specifically optimized for that. Also, JIMAGE can only be used at runtime. JIMAGE is still in its early years. There is very little developer information available; perhaps more will be exposed later.
Creating Custom Images
Java 9 supplies the jlink tool to create platform-specific runtime images. Custom images contain application specific modules and the required modules of the platform. Since the size of the runtime image aka JRE is reduced to bare minimum, the application image along with the JRE is also minimal. It bundles the JRE as a single unit of delivery along with the program. The jlink tool is located in the /bin directory of the JDK9 installed directory. There are several options available that are associated with this tool which can be used according to the requirement. The description can be obtained by using the –help option available with the jlink command. Here, it is extracted for convenience, or you can type jlink –help in the command line to obtain the following list.
Usage:
jlink <options> --module-path <modulepath> --add-modules <module>[,<module>...]
Option | Description |
–add-modules <mod>[,<mod>…] | Root modules to resolve |
–bind-services | Link in service provider modules and their dependencies |
-c, –compress=<0|1|2>
–disable-plugin <pluginname> –endian <little|big> |
Enable compression of resources: Level 0: No compression Level 1: Constant string sharing Level 2: ZIP Disable the plug-in mentioned Byte order of generated jimage (default:native) |
-h, –help –ignore-signing-information
–launcher <name>=<module>[/<mainclass>] –limit-modules <mod>[,<mod>…] –list-plug-ins |
Print this help message Suppress a fatal error when signed modular JARs are linked in the image. The signature-related files of the signed modular JARs are not copied to the runtime image. Add a launcher command of the given name for the module and the main class if specified. Limit the universe of observable modules. List available plug-ins. |
-p, –module-path <path> –no-header-files –no-man-pages –output <path> –save-opts <filename> |
Module path Exclude include header files Exclude man pages Location of output path Save jlink options in the given file |
-G, –strip-debug –suggest-providers [<name>,…] |
Strip debug information Suggest providers that implement the given service types from the module path |
-v, –verbose –version @<filename> |
Enable verbose tracing Version information Read options from file |
A Simple Example
Here, we’ll walk through a very simple program from the beginning to the end of how to create a runtime image of a Java application. We’ll assume that JDK9 is properly installed and the PATH and JAVA_HOME environment variable are set appropriately. In my case, it is installed and set as follows (on a Linux Platform):
- JDK9 installed directory /usr/lib/jvm/java-9-oracle
- PATH is set to /usr/lib/jvm/java-9-oracle/bin
- JAVA_HOME is set to /usr/lib/jvm/java-9-oracle
Step 1
Create a directory named /Home/SampleProject and a src directory inside it (for example, /Home/SampleProject/src).
Step 2
Create a directory named org.app.test inside the src directory (as an example, /Home/SampleProject/src/org.app.test).
Step 3
Now, inside the org.app.test directory, create a file named module-info.java. And, type the following contents:
module org.app.test{ requires javafx.controls; exports org.app.test; }
Step 4
Now, create a directory named org, app, test one inside another (for example, org.app.test/org/app/test) and then create a file named MyApplication.java inside the test directory and type the following contents:
package org.app.test; import javafx.application.Application; import javafx.scene.control.Alert; import javafx.scene.control.ButtonType; import javafx.stage.Stage; public class MyApplication extends Application{ public static void main(String[] args) { Application.launch(args); } @Override public void start(Stage stage) throws Exception { Alert alert = new Alert(Alert.AlertType.INFORMATION); alert.setTitle("Congratulation!"); alert.setHeaderText("You did it. The application is running. Press OK to close"); alert.setContentText("You have successfully created a custom image"); alert.showAndWait().ifPresent(rs -> { if (rs == ButtonType.OK) { System.out.println("Pressed OK."); } }); } }
Step 5
Create a /Home/SampleProject/mods directory. Here, we’ll save the compiled code. Compile the source code as follows. The present working directory is /Home/SampleProject.
javac -d mods --module-source-path src src/org.app.test/module-info.java src/org.app.test/org/app/test/MyApplication.java
Step 6
Now, let’s create the JAR file and store it in the lib directory (such as /Home/SampleProject/lib) as follows:
jar --create --file lib/org.app.test.jar --main-class org.app.test.MyApplication -C mods/org.app.test .
Step 7
To test, run the application as follows:
java --module-path lib -m org.app.test
Step 8
Now, let’s create the JMOD file and save it in the jmods directory (for example, Home/SampleProject/jmods):
jmod create --class-path lib/org.app.test.jar jmods/org.app.test.jmod
Step 9
Finally, let’s create the custom image by using following command:
jlink --module-path /usr/lib/jvm/java-9-oracle/jmods/:jmods --add-modules org.app.test --launcher runapp=org.app.test --output dist
This will create a folder named dist which contains all that is required to run the application. The program, with the launcher name given as runapp, is contained in the dist/bin directory. Double-click it to run.
Figure 1: The program is running
That’s all.
Conclusion
Refer to appropriate documentation for detailed description on each of the command options used in this article. Try a simple program and build it from beginning to end. This would build up the necessary confidence for experimenting with a little more complicated program, such as using more than one module and dealing with their dependencies. JDK9 has empowered developers to prepare its runtime baggage to be filled with only necessary modules. This is amazing and excellent, something most of us has been expecting from Java for a long time. A Java application now can shrug off the baggage it had to carry for apparently little reason and become platform optimized in distribution.