Try to speed up Java console program startup with GraalVM native-image

Overview

In this entry, the function of "[Native Image]" (https://www.graalvm.org/docs/reference-manual/native-image/) of GraalVM Uses to deal with shortening the startup time of Java console programs.

In summary

After doing this and that, the program that took about 1 second to start in the JVM with the performance of a specific computer (equivalent to a micro instance of OCI) became a native binary, and the start became about 100 ms.

Assumed reader

--Those who have basic knowledge about Java --Those who are interested in creating native binaries for GraalVM

Thing you want to do

--I have a console application written in Java. It was written by the author in another entry "Use JLine when you want to handle key input on the console character by character in Java" (https://qiita.com/hrkt/items/885f1c3526af03939c54). --I'm using JLine3 because I want to handle console keystroke events character by character. When JLine3 uses JNI to use platform-specific features, it uses the JANSI version of the terminal (so you need to do something like this entry). --I just want to see how fast Native Image can be

Preparation

environment

--You can use Linux (x86_64) or macOS. This entry uses the same one as the separate entry "Try running VS Code on cdr / code-server and Always Free Micro instances of OCI" (https://qiita.com/hrkt/items/44c78f71eb72f9d29f38).

Preparation of GraalVM and native-image

Like this, I introduced it under / opt. At the time of writing this entry, 19.3.1 is the latest version of GraalVM.

cd /opt
sudo curl -L -O https://github.com/graalvm/graalvm-ce-builds/releases/download/vm-19.3.1/graalvm-ce-java8-linux-amd64-19.3.1.tar.gz
sudo tar zxvf graalvm-ce-java8-linux-amd64-19.3.1.tar.gz

Use the GraalVM tool "gu" to introduce the tool for Native Image.

sudo  /opt/graalvm-ce-java8-19.3.1/bin//gu install native-image

For native-image to work, you need a set of toolkits to build the binaries on your target platform. Since this entry uses CentOS7, install the packages as shown below.

sudo yum install -y glibc-devel zlib-devel gcc

Prepare the target Java program

The program used in this entry is a simple console calculator. Click here for the release of GitHub. https://github.com/hrkt/commandline-calculator/releases/tag/0.0.4

cd master
../gradlew :calculator-cli-without-spring:shadowJar

Then you will have calculator-cli-without-spring / build / libs / calculator-cli-without-spring-0.0.1-SNAPSHOT-all.jar. This Jar file is necessary for execution using the "Gradle Shadow" plugin that brings together the self-made part and the Jar that has been used for the first time. It is a collection of files into one JAR file. The resulting JAR file was about 760KB.

$ ls -la calculator-cli-without-spring/build/libs/calculator-cli-without-spring-0.0.1-SNAPSHOT-all.jar
-rw-rw-r--. 1 opc opc 757383 Jan 31 23:13 calculator-cli-without-spring/build/libs/calculator-cli-without-spring-0.0.1-SNAPSHOT-all.jar

Run it and try running "1 + 1".

[opc@instance-20200117-1627 commandline-calculator]$ java -jar calculator-cli-without-spring/build/libs/calculator-cli-without-spring-0.0.1-SNAPSHOT-all.jar
Calculator is running. Press ctrl-c to exit.(boot in 1272 msec.)
1+1=
1+1
=2
q
[opc@instance-20200117-1627 commandline-calculator]$

At this time, you can see that it took about 1.3 seconds to start the program. The time is measured by the following method.

            RuntimeMXBean runtimeMXBean = ManagementFactory.getRuntimeMXBean();
            long uptimeInMillis = runtimeMXBean.getUptime();
            terminal.writer().println(String.format("Calculator is running. Press ctrl-c to exit.(boot in %d msec.)",
                    uptimeInMillis));

Build Native Image

Preparation

Unwind the JAR file created above and unzip it into your working space.

cd calculator-cli-without-spring/build/libs
mkdir inside
cd inside
jar xvf ../calculator-cli-without-spring-0.0.1-SNAPSHOT-all.jar

After that, create a directory to get the profile information for use in native-image.

mkdir -p META-INF/native-image

Get run-time profile

Before running native-image, run it as a normal Java program. By working with the agent, it collects information that cannot be obtained by static analysis, such as what kind of reflection is used at the time of operation and whether the JNI library is used.

/opt/graalvm-ce-java8-19.3.1/bin/java -agentlib:native-image-agent=config-output-dir=META-INF/native-image -cp . com.hrkt.commandlinecalculator.Main

When you execute "1 + 1 =" and enter "q" to exit the program in the same way as described above, the following information will be collected.

[opc@instance-20200117-1627 inside]$ ls -la META-INF/native-image/
total 16
drwxrwxr-x. 2 opc opc 109 Jan 31 23:15 .
drwxrwxr-x. 6 opc opc  88 Jan 31 23:15 ..
-rw-rw-r--. 1 opc opc 689 Jan 31 23:15 jni-config.json
-rw-rw-r--. 1 opc opc   4 Jan 31 23:15 proxy-config.json
-rw-rw-r--. 1 opc opc 366 Jan 31 23:15 reflect-config.json
-rw-rw-r--. 1 opc opc 472 Jan 31 23:15 resource-config.json

Create binary with native-image

Build. The meaning of the option is explained on the site. ](Https://www.graalvm.org/docs/reference-manual/native-image/)

 /opt/graalvm-ce-java8-19.3.1/bin/native-image  --no-server --no-fallback  --report-unsupported-elements-at-runtime -cp . com.hrkt.commandlinecalculator.Main

This takes time on the order of minutes. Let's wait patiently. The environment and target program for this entry will take approximately 5 minutes.

[com.hrkt.commandlinecalculator.main:13836]    classlist:  11,879.57 ms
[com.hrkt.commandlinecalculator.main:13836]        (cap):   3,604.46 ms
[com.hrkt.commandlinecalculator.main:13836]        setup:   9,898.60 ms
[com.hrkt.commandlinecalculator.main:13836]   (typeflow):  36,059.72 ms
[com.hrkt.commandlinecalculator.main:13836]    (objects):  23,227.98 ms
[com.hrkt.commandlinecalculator.main:13836]   (features):   3,516.18 ms
[com.hrkt.commandlinecalculator.main:13836]     analysis:  63,682.08 ms
[com.hrkt.commandlinecalculator.main:13836]     (clinit):   1,281.12 ms
[com.hrkt.commandlinecalculator.main:13836]     universe:   3,092.99 ms
[com.hrkt.commandlinecalculator.main:13836]      (parse):  10,531.88 ms
[com.hrkt.commandlinecalculator.main:13836]     (inline):  28,108.04 ms
[com.hrkt.commandlinecalculator.main:13836]    (compile): 176,012.21 ms
[com.hrkt.commandlinecalculator.main:13836]      compile: 227,565.33 ms
[com.hrkt.commandlinecalculator.main:13836]        image:   6,261.29 ms
[com.hrkt.commandlinecalculator.main:13836]        write:   2,044.20 ms
[com.hrkt.commandlinecalculator.main:13836]      [total]: 326,546.79 ms

Run

Let's move the completed binary. Let's start it several times to see how it looks. You can see that it can be started from the first half of the 2-digit msec to about 100 msec at the latest. It starts up much faster than it does in the JVM.

[opc@instance-20200117-1627 inside]$ ./com.hrkt.commandlinecalculator.main
Calculator is running. Press ctrl-c to exit.(boot in 66 msec.)
^C
[opc@instance-20200117-1627 inside]$ ./com.hrkt.commandlinecalculator.main
Calculator is running. Press ctrl-c to exit.(boot in 14 msec.)
^C
[opc@instance-20200117-1627 inside]$ ./com.hrkt.commandlinecalculator.main
Calculator is running. Press ctrl-c to exit.(boot in 16 msec.)
^C
[opc@instance-20200117-1627 inside]$ ./com.hrkt.commandlinecalculator.main
Calculator is running. Press ctrl-c to exit.(boot in 24 msec.)
^C
[opc@instance-20200117-1627 inside]$ ./com.hrkt.commandlinecalculator.main
Calculator is running. Press ctrl-c to exit.(boot in 131 msec.)
^C
[opc@instance-20200117-1627 inside]$ ./com.hrkt.commandlinecalculator.main
Calculator is running. Press ctrl-c to exit.(boot in 27 msec.)
^C

Let's check the binary.

First, let's check the type.

[opc@instance-20200117-1627 inside]$ file com.hrkt.commandlinecalculator.main
com.hrkt.commandlinecalculator.main: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=b5ce8429a80c97c4e12c87c516a8b24b59c64086, not stripped

It's just an ordinary ELF binary. What kind of library are you using ...

[opc@instance-20200117-1627 inside]$ ldd com.hrkt.commandlinecalculator.main
        linux-vdso.so.1 =>  (0x00007fff5abf9000)
        libm.so.6 => /lib64/libm.so.6 (0x00007f74c3bf7000)
        libpthread.so.0 => /lib64/libpthread.so.0 (0x00007f74c39db000)
        libdl.so.2 => /lib64/libdl.so.2 (0x00007f74c37d7000)
        libz.so.1 => /lib64/libz.so.1 (0x00007f74c35c1000)
        librt.so.1 => /lib64/librt.so.1 (0x00007f74c33b9000)
        libc.so.6 => /lib64/libc.so.6 (0x00007f74c2feb000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f74c3ef9000)

I'm using a very standard library for my Linux distribution. And the size is ...

[opc@instance-20200117-1627 inside]$ ls -la com.hrkt.commandlinecalculator.main
-rwxrwxr-x. 1 opc opc 10066064 Feb  1 04:08 com.hrkt.commandlinecalculator.main

About 10MB. It swelled considerably.

Summary

This entry dealt with trying to speed up the launch of Java console programs with GraalVM's native-image.

I've looked up to "startup", but keep in mind that it doesn't mention speed when it's running continuously after that.

Supplement

In order to use JNI, the program I wrote, I used JNA at first as I wrote in Another entry, but native -image could not be executed successfully. GitHub also had an exchange such as (Using JNA inside a native image does not work # 673) [https://github.com/oracle/graal/issues/673].

It seems that the Native Image side of GraalVM does not yet support the function using JNA used in this entry.

Recommended Posts

Try to speed up Java console program startup with GraalVM native-image
Try to link Ruby and Java with Dapr
Try to implement TCP / IP + NIO with JAVA
Try debugging a Java program with VS Code
Try connecting to AzureCosmosDB Emulator for Docker with Java
Try building Java into a native module with GraalVM
Java program to resize a photo into a square with margins
[Beginner] Try to make a simple RPG game with Java ①
Java to play with Function
Try DB connection with Java
Try gRPC with Java, Maven
Connect to DB with Java
Connect to MySQL 8 with Java
Input to the Java console
Java to learn with ramen [Part 1]
[Java] Points to note with Arrays.asList ()
Dare to challenge Kaggle with Java (1)
[Rails] How to speed up docker-compose
Try using Redis with Java (jar)
I tried to interact with Java
Try to imitate marshmallows with MiniMagick
[Java] Try to implement using generics
Try to extract java public method
Try bidirectional communication with gRPC Java
Try to implement Yubaba in Java
Java, arrays to start with beginners
I tried to measure and compare the speed of GraalVM with JMH
When I try to sign up with devise, it automatically redirects to root_path