This is the third part of a series about developing a native x86 microservice in Java. And it is about making an x86 binary from a Java program.
As already mentioned I wanted to use GraalVM for this. GraalVM is a
project that was based on OpenJDK 8. As there was already a ticket
to port to a later version the version information is hopefully
outdated when you read this lines. GrallVM is also a project with
many goals. Go to their website to inform yourself about it. But I
will only focus on the ability to use ahead of time compilation to
create native executables.
To create a native executable the GraalVm provides a tool called
native-image. The native-image command needs to get your program as
a argument like the java command. The easiest way for this is to
create a fat jar and call native-image -jar
your-program-fat.jar. Unfortunately the chances to get an
error message instead of a working executable are pretty high.
That's because native-image supports no reflection (except with some
help) and also forbids other constructs.
E.g. I had to switch from H2 db to Hsqldb because H2 db uses " a
direct/mapped ByteBuffer" and "A direct ByteBufer has a pointer to
unmanaged C memory, ...". Hsqldb doesn't use such a ByteBuffer and
thus doesn't break the build.
As already mentioned native-image doesn't support reflection out of
the box. While I could rewrite the usage of Jdbi to not use
reflection neither GSON nor Hsqldb could be changed to work without
it. Fortunately reflection is so widely used that they had created a
solution for this problem. With the parameter -H:ReflectionConfigurationFiles
you can provide a json file. In the json file you can specify the
class, the methods of the class and when needed the fields that
should be available for reflection.
The bad part of this is that you get only one reflection error after
the previous one was fixed. My 5,5MB fat jar took over 2 minutes to
compile. So the cycle was fix, wait 2 minutes for the compile and
then fix the next error and so on.
Be also warned that I've read the the compile time is exponentional
to the jar size. So be warned when trying larger frameworks.
After the native-image created an executable I had to find out that
Hsqldb contains some *.properties and *.sql files it needed to
initialize. By default they aren't included into the executable. To
solve this I had to use -H:IncludeResources=.*.properties|.*sql.
During my many trials I've found out that native-image tries to use
a server by default. That server performed sometimes unwanted
caching. To get rid of it I've used the --no-server
option. I've noticed no longer compilation time but my sanity level
native-image also has a --static option. This creates an
executable that references no shared libraries. Pretty handy when
putting things into containers.
Another strange part of the static option is that the generated
executable gets smaller. Normally the files get larger but not in
this case. The executable without the static file is 34MB while the
one with the static option is 29MB.
Also the size of the executable is pretty large it starts in under
100ms. That's the startup time you want when millions of people go
to your website after a successful commercial on TV.
This post is part of a series: