Conf42 Site Reliability Engineering 2021 - Online

Chasing the Grail

Video size:


JDK 16 features full musl support but doesn’t include AOT and Graal JIT. Is it all gone? Can you still write your own JVMCI compiler? The GraalVM licensing model is changing, and alternatives appear fast. In his talk, Dmitry Chuyko shows what you will achieve with native image. He’ll look into the practices of building tiny and performant microservice containers using Graal and the associated tools.

The future is now. Don’t miss it!


  • You can enable your DevOps for reliability with chaos native. Create your free account at Chaos native Litmus Cloud. We will be talking about reaching effectiveness for modern Java applications and still keeping them reliable in modern deployments.
  • Alpine Linux is based on a special C library called muscle. In 4 seconds on 100 megabytes network you get working OpenJDK in a container. The key here is having debug symbols. This is not a debug build of JDK, but debug symbols can be included with release build.
  • JDk eleven and JDK eight are still actively maintained long term supported releases. Few vendors provide ports of alpine muscle support to that older releases. Bellsoft also put some custom builds and container images for customers there.
  • There is an interface to plug in a special kind of dynamic compiler that helps to produce native images. The interface is called JVMCi that's an interface for Java based compilers. To support Alpine we had to support both GraalVM native image tool and also support for generated native images to work again in the alpine environment.


This transcript was autogenerated. To make changes, submit a PR.
Are you an SRE, a developer, a quality engineer who wants to tackle the challenge of improving reliability in your DevOps? You can enable your DevOps for reliability with chaos native. Create your free account at Chaos native Litmus Cloud hello, it's miece Shukong and we will be talking about reaching effectiveness for modern Java applications and still keeping them reliable in modern deployments. I work at a company named Bellsoft. We are among top OpenJDK enterprise contributors for minor releases and we release our own binary distribution of OpenJDK called Liberica JDK. I will mention it today, but all that I say is applicable to generality to any OpenJDK distribution. I guess so. Today we most typically use containers to offload routine task of reproducing same environment for all different applications we have assembled together. And we take help from systems like kubernetes and other systems that provide us other components to assemble all the production, the business logic, and also things like circuit breaker or fingers controller. Anyway, they're helpful. And inside containers that we produce we typically see layers starting from scratch and then having some os. In the case of Java world, of course some runtime and to run that JVM we need some os packages and we may also need some os packages to run the application itself. Maybe we have some native dependencies and then we have Java libraries. We typically have some framework that helps to connect to database to respond with some nice formatted output to our clients, whatever it makes. And then they have a tiny piece of the iceberg where our business logic, our application lives. And this is for classical container, the for JVM. And there may be another case then we use and when we produce native images using static compilation to decode machine code of the target platform. In this case we'll probably have larger tip of the iceberg, but we won't have additional things. We may even skip the OS layer sometimes. Then we link all necessary dependencies into the native image itself. So how small is that iceberg tip in the case of regular JVM deployment? If we go and if we imagine some spring application using spring boot framework and we go and create such an application using spring initializer, we will have a working project. We can compile it and get a 37 megabytes fat jar. That's all we need to run it on the JVMCI. But if we extract thin jar out of it, it will be as small as 3 kb, so it will be really extremely tiny. This is a piece of code that initializes our application, maybe starts from endpoint, that's it. Everything else is performed by the framework and then we wrap it into a container. How do we do that? Well, there are different ways. Sometimes we even see so called classical deployments and we create war files and they are deployed on the web server. Some people use out of the box functionality provided by the framework to produce the container, and still many developers or DevOps write docker files themselves. There's no wrong and right way here. All approaches have their drawbacks and have reasons to be used. But we can help in many cases from the side of vendor of JVM, or from other perspectives. Let's see. So as I mentioned, we need some parent image in all the cases to inject our application logic. On top of that and from JDK vendor perspective, base image is always an image having Java runtime. So I will use base and parent image terms both for the same thing, and then we put Java runtime into the container image. It can be done in a different ways. Basically very simple options like we can drop a binary, extract it and create all necessary dependencies. That's one of the ways we can install some OpenJDK distribution from standard packages repository of the OS distribution, or we can plug in some additional repository and install the package from there. In all that cases, it's good to be sure that someone who provided the base container image for you to care about testing because there is a huge amount of compatibility testing for the case. Then we create a JDK distribution. Many tests are to be run and there are bugs and there may be fine issues that we will see later. Then we install something looking pretty standard, but we get some unexpected behavior. So that is very simple to get an OpenJDk for your Java service or groovy service or Kotlin service. We just can use docker pool and say okay, I need something called openjdk, get jit please. And it will work, no problem, you'll have some image. Well you probably already asked all the questions about testing and who provide that. Well anyway, in my circumstances with 100 megabit network it will be downloaded half of a minute. It really takes a long time to get to my machine. What's the problem actually then it is uncompressed. Jit takes half, more than half of a gigabyte on my disk. That's not something that I like. Well, it becomes even more scary and probably unreliable. Then you have massive deployment in your production. Imagine that you have 1000 deploys using simultaneously and you have quarter of terabyte flying around. Something may go wrong it may be easy and cheap, but for example, if you start chasing the main region boundaries with the traffic, or you start to break some limits, some quotas that you already have and that might be used by some neighbor services or some stuff that you put on the same machines where you deploy. Or maybe you don't have a public cloud issues, but you really have a lot of private cloud issues. Well in any case, such a massive overhead doesn't sound right. Well, obviously smaller containers help us in these situations. Smaller containers, small container images, base images, JVMci how do we get that? Well, there are different ways, different vendors, providers. We can go and search, but then we have to select some. Well we'll look at a couple of examples here. For example, someone may say okay, we are targeted to microservices, to cloud deployments, kubernetes, and someone else can say exactly the same. Well let's look at these examples in action. We can try two different containers with Java eight. It is still alive, long term support at the release of JDK and we take something that's expected to be the latest version and we see that in one case the version is not latest, which means we miss like a year of security updates in this base image. That's really not good, while it is smaller and look here, both images are smaller than the one that I showed you first. Well, what's inside? Something is missing. The smallest one doesn't have. So Jit is outdated and it doesn't have a diagnostic tool called Java Flight recorder or JDK flight recorder. It is a very convenient tool. You can go to Bellsoft's block for example. We have a detailed overview of how to squeeze useful information about your production using JFR and it's just not there. It is a free option in OpenJDK, but it's not included here. Or we can go another way. If we use spring, we can issue a simple comment spring build a container image for me and it will perform container image build and the output will be a container image in your local repository which is already layered and inside it has supported Liberica JDK. So if you building such images on a regular manner, you will have updated version with security updates and you can switch to some page support. But here you get the properly prepared container out of the box, which is also much smaller than we saw in the previous cases and it is a convenient way and diagnostic tools like JFR are already included here. Well this is great. Besides of JDK, the extremely important part of your base container image is the OS layer and options here are really different. First they are really different in size, there are humongous ones and there is one which is extremely slim, but still it has standard C library, JIt has a package manager and it has a shell so you can log in in a convenient manner, apply some diagnostic scripts, standard JDK tools, whatever. And this distribution is called Alpine Linux. Well it's slightly different. This distribution is based on a special C library called muscle. It also has something called Busybox which is a swiss army knife implementing all standard or many standard Unix tools. And JDK was ported to be able to work natively on opine Linux and other muscle based distributions in the manner when it doesn't require glipc library. So we only use muscle and that allows us to use that initially slim version of Alpine. There is also an option to use glipsy layer. It will be still small but it will be noticeably larger than the muscle version. So if we look what happened then we released some like in this case 15 version of JDK comparing other images that are also slim. The Alpine is a champion here. And by the way if you noticed few slides before, Alpine Linux is even smaller than distro less which is actually a distro image devout package manager and shell. Well pull time is only 4 seconds that this small image paste on Alpine. So in 4 seconds on 100 megabytes network you get working OpenJDK in a container. That's really cool and really fast. If you have faster network of course you get it even faster. Well for Opian there are different options. Again the landscape is big and here we compiler for example the berka with another base image which appears to be smaller. Well why doesn't happen? You see that RTjar and other jars that contain standard library classes are compressed in the case of other distribution. Well unfortunately for Java eight that leads to a performant hit. Then we start our application and then it uses some classes and definitely it does classes from standard library it start to load slower and moreover than we transfer such an image over the network. Yes it may take less size on disk but we transfer it compressed. Like for example if we connect to docker repository and if we compress something twice jit actually becomes larger. So the size of compressed image is worse in this case if it contains compressed chars. Okay, we sometimes need to profile our application to ensure that it's reliable or to fix some problems. Well there is a great tool in Java world called Async profiler which can be used and it combines Java stacks, the native stacks up to some kernel calls and we might try to use it. Okay, let's install some dependencies, run our application, find the process id connect well we see that it does something inside JVM. What's the problem? We can take the Berkeley and to see detailed description for what happens in this case C two server compiler and its internal parts are visible in the profile. The key here is having debug symbols. This is not a debug build of JDK, but debug symbols can be just included with release build into release build and they help profilers like Async profiler to show morphine great information about what application and runtime do in some workloads. So I mentioned port of OpenJDK to muscle and opine Linux. It was integrated on the timeline of JDK 16 and these days we made JDK 17. But JDk eleven and JDK eight are still actively maintained long term supported releases. So few vendors provide ports of alpine muscle support to that older releases. So such builds are also available. And don't forget about alternatives architectures isas like arm 64. They are also supported by this change. Okay, we discussed a couple of examples, but can it be simpler? What if we just take Alpine Linux and install OpenJdk there and we'll get that sweet small container with all standard components and we don't need to think of different vendors, we just select base OS image and use OpenJDK, include it there. Okay, we can try. Let's add OpenJDk eight and try to use standard diagnostic tool called jcommand. Jit allows scripting and provides a very nice text interface. So this is a really necessary diagnostic tool. It just can start. There is no ls for this command. Okay. Anyway it's there and we can find Jit and use fullpath to start it. And let's try to execute a very simple basic command. Let's find out the uptime of running JVM instead we'll get a stack trace. That's really weird. Let's instead take some tested distribution prepared as a ready made container. Well, no problem. We have that jcommand tool and we get the uptime of running JVM instantly. Sometimes we have issues with public services, different ones, and sometimes they just change the terms of service like it happened with Docker hub. For that cases it's good to have some proxying functionality to bypass the limits or to have a private registry that you can connect to and where you can ask for some slas. And in the case of Bellsoft we provide such a thing. We also put some custom builds and container images for customers there. For example if you have support contract and you have refined or you find a bug in JDK, we can fix it, run all the testing to ensure that the build that affix corresponds to a Java specification and then it can be released in a private repository. It is one of the channels to deliver such useful builds for customers. Then the fix is integrated into OpenJDK publicly, but there is no need to wait for having it in production. So you can also try early access builds there. At least they used to be provided in this repository. Also it is very essential to connect to such a registry. You just need some credentials and then you work with it. Just like this public Toky registry. You see some experimental things that they put there are related to container sizes and they also provide experimental optimizations for latency, for footprint, for throughput in such builds like interesting bug ports. We've dont in JDK eleven not for a long time ago. They include footprint savings. Here we take some typical spring boot service pet clinic sample application and we can just launch it put now load and measure how much memory does it require. And if we apply slightly updated version of JDK we have big settings in footprint for free. So we immediately made our production more reliable. The JDK is tested in the same way as the released version. So this preview early access is just for some extra features, but it goes through the same pipeline of testing, so still secure and we get that savings. So when we select the container and the way we build it, there are a few interesting big roads like we may decide to take native image road that has its known limitations. Like for example we won't be able to use standard diagnostic tools from JDK and we will have the penalty. Then we update our running images frequently. So in case then we use classical JVM and we have very small iceberg tip, we can update only that thin layer and it will be extremely effective. In case of native image we'll have to update everything, almost everything. But at the same time it is better for new nodes because we don't need to transfer the entire big Asperg, we'll transfer smaller one the native image and we'll see the comparison what size difference. Then we use it. So for classical JVM containers we also have options. Like I mentioned LPN Linux you may have something against MuSL or Busybox or APK package manager. It may be just not familiar environment. Okay then you can take something more well known like yam package manager or apt or you need glipsy which is also available in opine case and in other cases too. So what about native images and the ways to create them? Native images are about static compilation and static compilation was added to JDK to OpenJDK some time ago and now it's been removed in the form that it has been implemented earlier. But there is still can interface to plug in a special kind of dynamic compiler or compiler that helps to produce native images. The interface is called JVMCi that's an interface for Java based compilers. And there is a project called GraalVM where Graal compiler is being developed and it's not stopped. So these changes, they just mean that source code of Graal compiler is not anymore dropped from GraalVM repository into OpenJDK repository and no developer resources are spent for doing that. But instead you can use GraalVM based technologies and we will see how to do that. If I talk about GraalVM builds from Oracle, there are two big options. One option is Graal VM Community edition builds which are provided the basic free license. And there is the GraalVM Enterprise Edition which is provided using Oracle OTN and same licensing policies like Java C. We decided to give the community something that will cover the task of native image generation, particularly for containers. This is a subset of what GraalVM world gives you because there is an option to have a dynamic Grail compiler for mixing languages and we focused on native images and we think that support for few extra platforms is important, like Alpine muscle here, arm 64 languages support for native images. It's still interesting and we still include that into Liberca native image jit. And also in the heart of this tool of this kit there is Liberica, the LTS version of JDK. To support Alpine we had to support both GraalVM native image tool as a compiler to work in such environment, and also support for generated native images to work again in the alpine environment. There are multiple aspects of it, but in the end all this has been implemented and you can download the binaries just like you can download the JDK binaries. And well, how to use that, there are a few good ways. Let's again look at the example. We can go and create sample microservices even smaller. Its fat jar will be as small as 17 megabytes. Fin jar again will be much much smaller, kind of like 32. We can switch to native image generation. What are good roots here. And what will be the difference between classical JVMCi container produced from such a services, such a service and native image containers? Well first of all you can go to spring initializer and then you create such a microservice. You can now check spring native support and you will have prepared configuration for maven or gradle that you can just use out of the box. But what's inside? Well of course you will need special plugin for your for Springboot and you will need that plugin to generate some things ahead of time that's not the static compilation into machine code itself. This is generation of some Java code which is necessary to get rid of some dynamic behavior which is too complex for a static compiler that will produce the machine code. And also you will need spring native. You will get spring native plugin of some version. Currently the support of newer version was announced and you will need to include GraalVM plugin in some cases. In basic case JIT is used under the hood by spring native, but you may want to take more control and we'll look at the examples. So here it is activated only for a special profile. This special case we invoke this profile and we say okay, let's create a native image and the specific version of native image tool will be used in this case if you don't want this complexity, but we in this case agree to get silent upgrades of the tooling, or at least we get the version of native image tool bound to the version of spring native tooling. We can invoke again same standard out of the box command line provided by spring plugins, but in the native image mode. And in this case we'll get a container built by Liberica native imagekit core of some version. Or again we may want more control and we may want the most effective alpine Linux container for this environment. They can manually recreate the build all necessary dependencies and then we invoke standard building commands, sorry, standard native image JIT commands to install native image support. If it's not Liberica native image JIT core, which is basically targeted only to creation of native images from Java programs and here we can compile the service itself with a simple comment and finally we get the resulting native image. In all that cases it will be smaller than 60 megabytes, which is really cool because we have for example in the case of alpine Linux only 5.6 megabyte base image. Then we have that dependencies for running the native images. It is extra seven megabytes and then we have that 57 megabytes on top of it and that resulting container will start in one 20th of a second, probably faster on server hardware. And what is also sweet here, if we get back to the original footprint comparison, we can take pet clinic, start it, measure the same plot and we have even more memory saved. For the case of using native image, it is really easy to try. Just take spring native and use standard build packs or go and download the Berry Connective image kit and try your production services and thank you for your attention.

Dmitry Chuyko

Senior Performance Engineer @ BellSoft

Dmitry Chuyko's LinkedIn account Dmitry Chuyko's twitter account

Awesome tech events for

Priority access to all content

Video hallway track

Community chat

Exclusive promotions and giveaways