[Guide] Creating a minimal JRE for Spigot (Great for container environments!)

Discussion in 'Systems Administration' started by Holosym, Nov 6, 2019.

  1. Hello everyone! :)

    I've created this guide to help ya'll reduce the resources your servers use. This is especially important if you're running Spigot servers inside containers.

    The final outcome we end up with is bringing our JRE size from 112 MB all the way down to 54 MB. That's a savings of over 48%!

    But wait. Hold up. How would I bring down the size of my JRE? After all, wouldn't that break things? And for that matter, why even bother?

    Let's begin by addressing the first question. How do we do this?

    A brief historical overview
    With the release of Java 9 we got something called Project Jigsaw. This basically allows us to customize the JRE with the specific features (modules) of Java we actually need; effectively reducing the size of the JRE entirely.

    With time it's gotten more mature and includes better features, functionality, and documentation.

    Getting things ready
    Before we can do anything, we need a version of the Java JDK that actually supports the module system. We use the command line tools provided by the JDK to actually create the JRE later on.

    I'm going to be using OpenJDK Java 11 with (OpenJ9). I like the use of OpenJ9 vs. Hotspot because it has a smaller memory footprint. Though, any version of Java JDK 9 or above should work; Hotspot or not!

    The next thing we need is a freshly built Spigot server jar; obtain one like you normally would with the BuildTools. :)

    Creating the itty-bitty JRE
    Now that we have everything we need, let's get started!

    I'll start by creating a working directory so we have everything in one place.
    Code (Text):
    mkdir ~/spigot-small-jre/ && cd $
    Then, I'll copy my spigot.jar into this directory.
    Code (Text):
    cp /myBuildToolsDirectory/spigot.jar ./
    Now time for the important part. We need a way to determine what modules Spigot needs from the JRE to run. This can be obtained from the executing "jdeps" command.
    Code (Text):
    jdeps --list-reduced-deps ./spigot.jar
    Which outputs:
    Code (Text):
    The "java.base/sun.security.util" and "java.base/sun.security.x509" dependencies can be removed from the list because all Java applications are expected to use the "java.base" module. We don't need to specify it.

    Voila! We know what we need now! Except, we're missing something. I'm not entirely sure why (maybe one of you know ;)) but it doesn't show me a module Spigot actually needs. I'll elaborate on that more, but suffice it to we'll need to add this to the list of modules we need:
    Code (Text):
    Okay. Fun part now, building the JRE! This uses the "jlink" command which we'll run like this:
    Code (Text):
    jlink --no-header-files --no-man-pages --compress=2 --strip-debug --add-modules java.compiler,java.desktop,java.management,java.naming,java.rmi,java.scripting,java.sql,jdk.sctp,jdk.unsupported,jdk.zipfs --output ./jre
    And... done! That's it! :) Let's take a look at this command real quick and discuss more about that error I mentioned.

    We specify
    Code (Text):
    --no-header-files --no-man-pages --compress=2 --strip-debug
    to compress files down and remove files we really don't need.

    Code (Text):
    --add-modules java.compiler,java.desktop,java.management,java.naming,java.rmi,java.scripting,java.sql,jdk.sctp,jdk.unsupported,jdk.zipfs
    to tell JLink which modules we want our minimal JRE to include.

    Why do we actually include the "jdk.zipfs"? Because without it, the Spigot server crashes with:
    Code (Text):
    java.nio.file.ProviderNotFoundException: Provider "jar" not found
    I simply used a bit of thinking to know that accessing a JAR file would likely require some sort of compression module (because JAR files are zipped up) and saw "jdk.zipfs". And... that worked! :unsure:

    The last bit
    Code (Text):
    tells JLink which directory to put our newly built JRE files in. Very familiar setup to a JRE you actually install!

    Okay! Now let's run Spigot with our JRE, finally! Just use the freshly built JRE "java" binary:
    Code (Text):
    ./jre/bin/java -jar spigot.jar
    Congratulations! You created a lightweight JRE that can run a Spigot server. :cool:

    We actually were able to reduce the JRE size without breaking anything because we told Java about all the modules we required. Everything else simply wasn't needed.

    I have found this feat to be greatly beneficial towards a container environment. I can reduce my container image size and resource footprint in the real world. This is a big deal! :eek:

    Hopefully you've gotten something from this and you're able to utilize the modular system of Java to your advantage. Happy crafting! :giggle:

    • Like Like x 1
    • Informative Informative x 1
  2. I didn't know we can do such things :eek:
  3. Note that the size reduction is minimal even when running several servers, since Docker does not duplicate files that came with the initial image and were not overwritten by the containers (this means that all the containers will point to the same JVM files). Besides, I doubt the resource footprint is actually reduced (maybe only barely).
  4. Oh shoot! You're very much right!

    I started using Docker a few days ago and realized today that it uses something called Kernel Same Page Merging. Which actually works quite well for the JRE. Thanks for the catch :D

    I did a few tests and found that a minimized JRE seems to shave around ~1% of actual memory usage. CPU seems to remain the same.

    Anyways, I'm trying to figure out how I can actually edit my post to clarify some details I got off from my confusion. Thanks for the help!
  5. MiniDigger


    If you care about size, I would recommend just running azulus alpine builds. They provide support for a TCK compliant openjdk 11 Alpine build (something Oracle does not since project portola is underfunded, altho these builds are based on portola). Their whole image is like 140mb and that includes all layers, not just the jdk.
    I would advice against running limited jre builds since you will most likely run into incompatibilities and it's just not worth the extra effort for a small mb.
    Like, I work at a multi million insurance company, our devops thought it would be cool to roll out own jre builds (and that's a team of like 20 who got together and build something) and even that created issues left and right, broke our APM, broke encryption and just generally caused a lot of wasted hours of dev time.
  6. This is good stuff! Thanks for the insight!

    Hmm, Project Jigsaw seems like a big let down then... It seems like the only real good use for it is in an embedded system? Do you think there's much use at all in trying to make smaller images then? If they all utilized KSM what's the point? :unsure:
  7. Whilst your title says '...for Spigot'

    You should be aware that Minecraft currently runs on Java 1.8, and has done so since MC 1.8
    Maybe there will be a future incarnation which does use a higher JVM. (I am not aware of any plans for this at this time).

    You need to emphasize that this is intended for JVM's of 1.9.x and above, as the mechanics that you are using are not available to any lower version.
  8. MiniDigger


    It's 2019. Java 14 is in rampdown phase 1. You really still stuck on java 8 for running your server?
    Yes, Minecraft ships java 8, but runs on the latest LTS, java 11, just fine. So does the Minecraft server and all its forks (craftbukkit, spigot, paper...).
    Java included substantial new features and performance improvements, especially in 10 and 11, so you really should consider upgrading to make use of those.
    Heck, even at work, insurance company, full with legacy stuff, we already run many applications on java 11...
    • Agree Agree x 3