Introduction to jlink
Naturally, Question 1 is: What is jlink
?
TL;DR: It’s a tool for creating small custom JVMs containing only the bits you need. They take less disk space and start faster than the full JVM.
Full Answer:
You can use the jlink tool to assemble and optimize a set of modules and their dependencies into a custom runtime image.
Indeed the simplest way to use jlink
is to use Java modules yourself. There is a lot written about modules elsewhere so I won’t add to the pile, just assume you know roughly what they are. One critical point: modules have to state which other modules they depend on. The JDK itself is now modularized with the dependencies explicit (visualised here). I shudder to think about how much hard work went into that!
If you package your code as a module then your code and its dependencies (including transitive ones) can be isolated from unused modules and a custom JVM can be created containing only necessary modules. That’s what jlink
does.
If you do not package your code as a module then you can use the jdeps
tool to identify module dependencies and have jlink
create a JVM with only those modules you specify.
Don’t use java.xml
? Don’t need it. Don’t want swing? Don’t have it then. Simple.
(Edit: This section used to incorrectly claim that modularising your code was the only way to use jlink
. Thanks Claes for pointing out my error)
For the rest of this post I’ll show you how to create a minimal Java module and use jlink
to create a minimal JVM image. I’ll chuck in some measurements too, free of charge.
A minimal Java module
Create a source directory
$ mkdir -p src/mjg123.module/mjg123/module
$ cat > src/mjg123.module/module-info.java
module mjg123.module {}
$ cat > src/mjg123.module/mjg123/module/Main.java
package mjg123.module;
public class Main {
public static void main(String... args){
System.out.println("Hello from mjg123.module");
}
}
$ tree src
src
└── mjg123.module
├── mjg123
│ └── module
│ └── Main.java
└── module-info.java
Our module has no dependencies, except the implicit dependency on java.base
.
Compile it:
$ javac -d mods/mjg123.module \
src/mjg123.module/module-info.java \
src/mjg123.module/mjg123/module/Main.java
We have made a module! Yeah!
$ tree mods
mods
└── mjg123.module
├── mjg123
│ └── module
│ └── Main.class
└── module-info.class
Running our module
We can run it using the --module-path
and -m
options:
$ java --module-path mods -m mjg123.module/mjg123.module.Main
Hello from mjg123.module
We have ourselves a module, and I do believe we’re ready to use jlink
!
Using jlink
$ jlink --module-path $JAVA_HOME/jmods:mods --add-modules mjg123.module --output linked
It takes a couple of seconds to create the new binaries in linked/bin
, and we run the code just as before but using the new java
binary. We don’t need to specify --module-path
because it’s already linked in, and removing it will elide some runtime cost of scanning the module path.
$ linked/bin/java -m mjg123.module/mjg123.module.Main
Hello from mjg123.module
Measurements
Module count
$ java --list-modules | wc -l
99
$ linked/bin/java --list-modules
java.base@9.0.1
mjg123.module
Disk usage
My $JAVA_HOME
is 557Mb. How about the new one?
$ du -sh linked
45M linked
There is some more stuff we could trim away in there, but this is already a lot smaller. If you’re distributing your code with the JVM bundled with it (eg in a container image) then the benefit is clear.
Execution time
If you read my previous posts you’ll know that I am mildly obsessed with startup time. Here’s a couple of quick measurements:
Full JDK distribution:
$ perf stat -r50 java --module-path mods -m mjg123.module/mjg123.module.Main
...snip...
0.204575188 seconds time elapsed ( +- 1.39% )
As shrunk by jlink
:
$ perf stat -r50 linked/bin/java -m mjg123.module/mjg123.module.Main
...snip...
0.122966645 seconds time elapsed ( +- 1.09% )
40% improvement with no extra effort. We can also use CDS trivially:
$ linked/bin/java -Xshare:dump
...ignore warnings about missing classes...
$ perf stat -r50 linked/bin/java -Xshare:on -m mjg123.module/mjg123.module.Main
...snip...
0.098581867 seconds time elapsed ( +- 1.65% )
BAM! Another 24ms gone - we’re twice as fast as before!
Conclusion
If you’re able to specify which Java modules you need, jlink
is an easy way to improve the size and speed of your Java apps. I would expect AOT and AppCDS to make it even faster. If startup time is a critical factor in your application then it makes modules a very appealing feature of the JDK.
The ever-excellent Trisha Gee has some advice about migrating existing code into modules.
Thanks for help writing this post: Alex Bransby-Sharples, Claes Redestad, Alan Bateman.