#GraalVM and native-image

8 messages · Page 1 of 1 (latest)

quiet orbit
#

GraalVM is an alternative JVM maintained by Oracle/OpenJDK.

GraalVM is not only able to execute JVM bytecode but also other languages like JS, R, Ruby, python or LLVM-code.
In addition to the JIT-compilers provided by Hotspot, GraalVM offers a third JIT which is implemented in Java whereas the Hotspot JITs are implemented natively.
A well-known feature of GraalVM is native-image. This allows to compile a Java application to a native executable which does not require a JRE to run.

GraalVM Community Edition can be downloaded from https://github.com/graalvm/graalvm-ce-dev-builds/releases/.
After downloading and installing GraalVM, it is possible to install additional components using the gu (GraalVM Updater) program.
In order to install native-image, one needs to run gu install native-image and possibly install additional prequisites (https://www.graalvm.org/22.0/reference-manual/native-image/#prerequisites).

#

Creating native executables

After creating a runnable JAR file, it is possible to create a native executable from that JAR file using the command native-image -jar THE_JAR_FILE.jar.
Alternatively, it is possible to create a native image from a single class using the command native-image fully.qualified.name.of.MainClass in the classpath root.

Reflection and JNI configuration

If the compiled application uses Reflection at runtime, the reflectively accessed elements need to be known at compile-time. It is possible to specify them using by creating a JSON file listing these elements. This JSON file can then be passed to native-image using the argument -H:ReflectionConfigurationFiles=/path/to/reflect-config.json.
Such a file could look like the following:

[
  {
    "name" : "fully.qualified.name.of.SomeClass",
    "methods" : [
      { "name" : "<init>", "parameterTypes" : [] }
    ]
  },
  {
    "name" : "fully.qualified.name.of.OtherClass",
    "methods" : [
      { "name" : "<init>", "parameterTypes" : ["java.lang.String"] },
      { "name" : "test", "parameterTypes" : ["java.util.List"] }
    ]
  },
  {
    "name" : "fully.qualified.name.of.YetAnotherClass",
    "allDeclaredMethods" : true,
    "allDeclaredConstructors" : true,
    "allDeclaredFields" : true
  }
]

In this example, the no-args constructor of the class fully.qualified.name.of.SomeClass as well as the constructor fully.qualified.name.of.OtherClass taking a String argument, the method test(String) of the same class and all methods, fields and constructors of fully.qualified.name.of.YetAnotherClass can be accessed using Reflection.

If the application uses JNI and some Java classes/methods/constructors/fields need to be accessed from JNI, one needs to create a JSON file like the one for reflection and list all elements which should be accessible from JNI. This file can then be passed to native-image using -H:JNIConfigurationFiles=/path/to/jni-config.json.

quiet orbit
#

Resources

Something similar also holds for resources accessed within native-image. In order to access a resource using Class#getResource or ClassLoader#getResource, one needs to specify the resources in a JSON file as well. This file could look like this:

{
  "resources": {
    "includes": [
      {"pattern": ".*/important/.*txt$"}
    ],
    "excludes": [
      {"pattern": ".*/ignore/.*txt$"}
    ]
  }
}

This would allow accessing all resources in a directory named important ending with .txt except they are in a directory called ignore.
native-image can be informed about this resource files using the CLI argument -H:ResourceConfigurationFiles=/path/to/resource-config.json.

chrome coral
#

I would like to highlight one of the advantages from using native-image - it's the final size of the application. Thanks to some clever parameters it is possible to create "hello world" application, which will have only 1.5MB.
All the credits go to the video below. I found it very useful and interesting so I think you will find it interesting too.
https://youtu.be/6wYrAtngIVo

Containers are increasing the way Java applications are packaged and deployed. There are lots of qualities desirable in a containerized application like fast startup and low memory and CPU requirements, but size and security are also important considerations. Moving big container images around a network will increase the time it takes to start u...

▶ Play video
indigo iron
#

How about the deployability with Docker image?

quiet orbit
#

Yes, that works properly and I might add something about that at some point

indigo iron
#

Thank you. Would be interested in hearing more about it, since our company services run only on Docker containers, orchestrated with Kubernetes.

quiet orbit
#

using native-image with Docker

By default, executables generated by native-image are dynamically linked. However, it is possible to create what is called a "static" executable which statically links all required native libraries.
After setting up a musl (which is an alternative to libc) toolchain, one can configure native-image to create a static executable by adding --static --libc=musl to the arguments of native-image.
Since libc is installed on most systems, it is possible to create a "mostly-static" executable which statically links everything required except libc and dynamically links libc. This is possible by adding -H:+StaticExecutableWithDynamicLibC as a command-line argument to native-image.
Documentation for static and mostly static images can be found here: https://www.graalvm.org/latest/reference-manual/native-image/guides/build-static-executables/

After creating a static executable, it should be possible to create a docker image that only contains the built executable:

FROM scratch
COPY built-executable /app
ENTRYPOINT ["/app"]

If one uses a mostly-static image, one would need to use a base image containing libc:

FROM frolvlad/alpine-glibc
COPY built-executable /app
ENTRYPOINT ["/app"]

When creating dynamically linked executables, one would need to make sure that all dynamically linked executables are present in the docker image.