Using jlink to cross-compile minimal JREs
jakewharton.com
Using jlink to cross-compile minimal JREs
1–10 of 66 posts
Re: Using jlink to cross-compile minimal JREs
#2Figuring out what modules you need to ship can be done with the jdeps tool, but, you have to watch out for some gotchas. One is that you have to run it on each JAR or set of jars. It's slow so it helps to do this incrementally. Another is that some critical JVM functions are implemented as 'plugins' that are loaded reflectively, and the static analysis won't find them. The one that trips up my users the most is jdk.crypto.ec which implements elliptic curve cryptography. The Java TLS stack will be present but fail to connect to many TLS servers nearly at random if this module is missing. Nonetheless it still needs less configuration than a typical ProGuard/R8/native-image style dead code elimination analysis.
JLink can replace JARs with an optimized file format called "jimage". It uses a very interesting perfect hash algorithm to minimize the number of seeks/page faults required to find a class or file and it also creates a unified string table. So the space savings come not only from deleting dead code but also reducing the space required. Unfortunately it only works for JARs that are explicitly modularized (with a module-info.class file). Slowly more modules are getting this but most still don't. A good improvement would be to use the jimage format for everything. It also does a bunch of other optimizations, for startup time and the like. Also you have to figure out which modules are modular and put them on the module path at link time. There are some build system plugins that can help with that but they mostly don't do cross-linking.
Final thing to realize is that jlink doesn't create a bundled app. It just shrinks and customizes the JVM for your app. To allow your app to start up, be installed etc requires other stuff.
Conveyor [1] has extensive support for jlink. You can name a JDK or it will learn from your Gradle build, then it'll download the JMOD files you need for each platform, run jdeps on all your JARs incrementally, figure out which modules are explicit, figure out which modules are broken and have to be put on the classpath, add back the TLS ECC support, allow you to override all these decisions via config, run jlink for each target OS and architecture, finally bundle up the results in self-updating packages for each OS and do it all in parallel. It's doing a lot of work but the result is pretty magical - you just take the JARs from your build system e.g. via the Gradle plugin, feed it to the tool and out pops a nicely optimized set of downloads with HTML download page for your cross-platform app.
Re: Using jlink to cross-compile minimal JREs
#3Re: Using jlink to cross-compile minimal JREs
#4Reflection or any other kind of dynamic execution (JNI?) will break this, no?
jlink is more similar to tree shaking: it strips the JRE of anything your program don't need.
Re: Using jlink to cross-compile minimal JREs
#5Reflection or any other kind of dynamic execution (JNI?) will break this, no?
No. You are thinking of GraalVM native image which will compile java to native machine code. jlink is more similar to tree shaking: it strips the JRE of anything your program don't need.
Re: Using jlink to cross-compile minimal JREs
#6Earlier quoted context omitted.
No. You are thinking of GraalVM native image which will compile java to native machine code. jlink is more similar to tree shaking: it strips the JRE of anything your program don't need.
But I think the parent's point is given reflection, how can jlink statically know the complete set of classes that your program needs?
Final binary size is naturally vastly reduced b/c you won't have the whole JRE, but last I tested you still end up with very chunky executables (minimal JFX GUIs were coming out to 100-200 MB)
Re: Using jlink to cross-compile minimal JREs
#7Earlier quoted context omitted.
But I think the parent's point is given reflection, how can jlink statically know the complete set of classes that your program needs?
It's only tree shaking the JRE itself, not your whole program (unfortunately..). So as I understand it, it means no dynamically calling arbitrary classes in the JRE, but that's a much narrower limitation. Final binary size is naturally vastly reduced b/c you won't have the whole JRE, but last I tested you still end up with very chunky executables (minimal JFX GUIs were coming out to 100-200 MB)
Re: Using jlink to cross-compile minimal JREs
#8Earlier quoted context omitted.
It's only tree shaking the JRE itself, not your whole program (unfortunately..). So as I understand it, it means no dynamically calling arbitrary classes in the JRE, but that's a much narrower limitation. Final binary size is naturally vastly reduced b/c you won't have the whole JRE, but last I tested you still end up with very chunky executables (minimal JFX GUIs were coming out to 100-200 MB)
Is there something that can do the tree-shaking of the Java program?
But to just say "no reflection" and tree shake your JVM code - not that I'm aware of unfortunately! I'd love to just treeshake entire unused dependencies. At the moment I do it manually - but it's a chore and it's hard to do comprehensively.
Now that post- Java8 you're supposed to jlink the JRE, I somehow doubt this will ever happen. The people that care about executable size would probably be doing Graal Native.
Re: Using jlink to cross-compile minimal JREs
#9Reflection or any other kind of dynamic execution (JNI?) will break this, no?
Re: Using jlink to cross-compile minimal JREs
#10If your application is modularised, it can become a part of the image, but, as the article shows, creating a custom runtime is easy and recommended even if your application is not modularised.
BTW, the JDK contains not just a Java runtime, but development tools, as well as an additional copy of the entire class library (this copy, in jmod files, is stored in a format that jlink uses as its input; i.e. it's there only to allow generating new runtime images). Use the entire JDK as an runtime is a real waste of space, since, among other things, it contains all libraries twice.
One minor comment, though. jlink produces Java runtime images or, in short, Java runtimes -- not a JRE. The name JRE refers to a particular kind of Java runtime from a bygone era when there was a global Java runtime environment, which was used by applets (and Web Start applications). When applets and Web Start were removed, the JRE and the very concept of one, was gone along with them. Some people still use the anachronistic term JRE to refer to a Java runtime (and some companies distribute pre-linked Java runtimes and call them JREs), but the real JRE is gone.