Java is not that slow and memory devouring as it used to be 20 years ago, but there’s still room for improvement. If you are looking for ways to accelerate your Java apps and reduce the size of container images, GraalVM is at your service!
The latest CPU release of Liberica Native Image Kit (NIK), a GraalVM-based utility for native images generation, includes an exciting new feature, namely the support for cross-compilation of musl-based Linux static images. Read on to know how static images will make your Java apps run at rapid-fire pace!
Benefits of static images
Static images are binaries statically linked against a C library: musl, glibc, or any other libc implementation. Static images have several essential advantages:
- Unmatched flexibility when choosing a base Docker image because they work even with FROM scratch base images, the only true distroless images that have always been unworkable for naturally dynamic Java applications;
- Accelerated startup due to the fact that no symbol resolution is performed at run time by the dynamic linker.
Note that with Liberica NIK, you can produce both static and dynamic images. Static images can sometimes be even smaller than dynamic ones, but everything depends on your application.
Benefits of musl libc
The most popular C library implementations at the heart of any Linux distribution are glibc and musl. glibc is the most widespread libc with great compatibility and optimal performance. But with 35 years of history behind it, its codebase is bloated and associated with significant overhead. musl has gained popularity in recent years as a minimalistic C library implementation perfect for cloud-native applications. Thanks to a much cleaner codebase, it has
- Small static and dynamic overhead leading to lower memory consumption;
- Lesser attack surface making container images more secure.
Two lightweight Linux distributions, Alpine and Alpaquita, are based on musl and help to reduce the size of Docker container images drastically.
Cross-сompiling musl-based images
Let’s now build a static image using Liberica NIK.
Installing NIK
If you are new to Native Image, download Liberica NIK version 22.3.1 with support for cross-compilation. Follow the instructions to complete the installation.
Installing Cross Toolchain
You need a musl-based gcc toolchain for cross-compilation. You can get it here (note that later versions might not work). Extract the toolchain to a selected directory (referred to as $TOOLCHAIN_DIR
later on).
Installing zlib
Unfortunately, this toolchain does not contain libz.a, which is needed for native compilation. The easiest way is to copy it from your host system: install a zlib development package (on Ubuntu, it is called zlib1g-dev
), then copy the installed libz.a into $TOOLCHAIN_DIR/x86_64-linux-musl/lib
directory.
Alternatively, you can build the library from source. Download a source package from the official zlib website, unpack it, change into the zlib directory, and run
CC=$TOOLCHAIN_DIR/bin/x86_64-linux-musl-gcc
./configure --prefix=$TOOLCHAIN_DIR --static
make
make install
Building Static Native Image
Everything is ready for cross-compilation! Make sure your toolchain’s bin directory is in PATH by running:
x86_64-linux-musl-gcc
You should get the output similar to:
x86_64-linux-musl-gcc is /…/x86_64-linux-musl-cross/bin/x86_64-linux-musl-gcc
First, let’s compile a regular glibc-based static image:
native-image --static -jar app.jar app-static-glibc
Now, a musl-based one:
native-image --static --libc=musl -jar app.jar app-static-musl
Finally, compare image sizes (your numbers will be different):
ls -l app-static-*
-rwxrwxr-x 1 user user 14114944 Jan 30 12:43 app-static-glibc
-rwxrwxr-x 1 user user 12208600 Jan 30 12:58 app-static-musl
The musl-based image is about 2 Mb, which is 13% smaller than a glibc-based one.