Playing with GraalVM on Windows 10 and WSL2

Tsuyoshi Ushio
7 min readMay 26, 2020

[GraalVM](https://www.graalvm.org/) is a Java VM and JDK based on HotSpot/OpenJDK, implemented in Java. Although, the project has a feature of supporting the polyglot programming, I’m interested in the performance improvement. It looks fantastic for a serverless enthusiast.

  1. To improve the performance of Java virtual machine-based languages to match the performance of native languages.
  2. To reduce the startup time of JVM-based applications by compling them ahead-of-time with [GraalVM Native Image](https://www.graalvm.org/docs/reference-manual/native-image/) technology.

However, is it true? I tried, Graal VM as a fundamental investigation.

  • Environment Setup on Windows 10
  • Compare the performance of Spring Boot application
  • Check if the Native Image works with class loader that load outside jars

Let’s get started.

Installation

Installation is easy. Just download GraalVM and set the JAVA_HOME and PATH.

For windows, you can find the binary.

Prerequisite

For using Native Image, it requires Microsoft Visual C++(MSVC). I installed with Visual Studio Installer.

Install MSVC v142

Install native-image

GraalVM includes gu command. That is GraalVM Updater.

gu install native-image

Start Developer Command Prompt for VS 2019.

Developer command prompt for VS 2019

Then I create a simple bat file that is executed on the command prompt.

setenv.bat

set JAVA_HOME=C:\Program Files\Java\graalvm-ce-java11-20.1.0
set PATH=%JAVA_HOME%\bin;C:\Users\tsushi\Codes\Graal\micronaut-1.3.5\bin;%PATH%

Now we are ready to go!

Spring Boot with Graal VM

I did the quick start with Dependencies to make the image bigger.

Follow the direction of the Quick start.

With configuring H2 database.

Then build jar file.

mvnw clean package

You can run the spring boot.

>java -jar target\demo-0.0.1-SNAPSHOT.jar.   ____          _            __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.3.0.RELEASE)
:

Compile the jar to the native image.

>native-image -jar target\demo-0.0.1-SNAPSHOT.jar
[demo-0.0.1-SNAPSHOT:53792] classlist: 6,018.57 ms, 0.96 GB
[demo-0.0.1-SNAPSHOT:53792] (cap): 9,223.27 ms, 0.96 GB
[demo-0.0.1-SNAPSHOT:53792] setup: 18,185.59 ms, 0.96 GB
[demo-0.0.1-SNAPSHOT:53792] (clinit): 639.49 ms, 1.71 GB
[demo-0.0.1-SNAPSHOT:53792] analysis: 37,602.74 ms, 1.71 GB
Warning: Aborting stand-alone image build due to unsupported features
Warning: Use -H:+ReportExceptionStackTraces to print stacktrace of underlying exception
[demo-0.0.1-SNAPSHOT:49108] classlist: 3,939.95 ms, 0.96 GB
[demo-0.0.1-SNAPSHOT:49108] (cap): 7,055.92 ms, 0.96 GB
[demo-0.0.1-SNAPSHOT:49108] setup: 15,429.38 ms, 0.96 GB
[demo-0.0.1-SNAPSHOT:49108] (clinit): 601.30 ms, 1.21 GB
[demo-0.0.1-SNAPSHOT:49108] (typeflow): 10,140.65 ms, 1.21 GB
[demo-0.0.1-SNAPSHOT:49108] (objects): 7,480.97 ms, 1.21 GB
[demo-0.0.1-SNAPSHOT:49108] (features): 499.95 ms, 1.21 GB
[demo-0.0.1-SNAPSHOT:49108] analysis: 19,279.06 ms, 1.21 GB
[demo-0.0.1-SNAPSHOT:49108] universe: 1,114.47 ms, 1.21 GB
[demo-0.0.1-SNAPSHOT:49108] (parse): 3,101.44 ms, 1.69 GB
[demo-0.0.1-SNAPSHOT:49108] (inline): 2,358.76 ms, 1.69 GB
[demo-0.0.1-SNAPSHOT:49108] (compile): 20,765.57 ms, 2.31 GB
[demo-0.0.1-SNAPSHOT:49108] compile: 30,618.68 ms, 2.31 GB
[demo-0.0.1-SNAPSHOT:49108] image: 2,506.89 ms, 2.32 GB
[demo-0.0.1-SNAPSHOT:49108] write: 2,015.20 ms, 2.32 GB
[demo-0.0.1-SNAPSHOT:49108] [total]: 75,704.00 ms, 2.32 GB
Warning: Image 'demo-0.0.1-SNAPSHOT' is a fallback image that requires a JDK for execution (use --no-fallback to suppress fallback image generation and to print more detailed information why a fallback image was necessary).

You can find exe image. Let’s execute it. It also start Spring Boot.

demo-0.0.1-SNAPSHOT.exe

NOTE: Official support for the GraalVM with Spring Framework will be 5.3. For more details. If you run on production, it might cause a problem. It will cause a problem. The native image looks faster than just using GraalVM. GraalVM has already faster than OpenJDK. It not significant, however, it must be reduce the cold start time.

2020-05-24 20:40:09.2712020-05-24 20:40:14.611 13 sec <- Run by jar file2020-05-24 20:40:57.1982020-05-24 20:41:02.405 5 sec <- Run by exe file

Size of the image is significantly different.

05/25/2020  06:17 PM        37,796,572 demo-0.0.1-SNAPSHOT.jar
05/25/2020 06:21 PM 7,353,856 demo-0.0.1-SNAPSHOT.exe

Micronaut

However, this guy said it is 45 times faster when he tried micronaut. Let’s Try it. I follow the second tutorial. Micronaut officially support GraalVM.

Create a jar file.

gradlew assemble

on the build\libs directory, you will find jar files. Use the same native-image and compile it to native. This time, the exe image is bigger than jar file.

05/25/2020  02:50 PM        54,554,112 hello-world-0.1-all.exe
05/25/2020 02:43 PM 12,853,755 hello-world-0.1-all.jar

Run these.

hello-world-0.1-all.jar (6000ms)

>java -jar hello-world-0.1-all.jar
←[36m18:46:29.760←[0;39m ←[1;30m[main]←[0;39m ←[34mINFO ←[0;39m ←[35mio.micronaut.runtime.Micronaut←[0;39m - Startup completed in 6000ms. Server Running: http://localhost:8080
←[36m18:47:18.768←[0;39m ←[1;30m[Thread-1]←[0;39m ←[34mINFO ←[0;39m ←[35mio.micronaut.runtime.Micronaut←[0;39m - Embedded Application shutting down

hello-world-0.1-all.exe (1738ms)

>hello-world-0.1-all.exe
←[36m18:47:31.652←[0;39m ←[1;30m[main]←[0;39m ←[34mINFO ←[0;39m ←[35mio.micronaut.runtime.Micronaut←[0;39m - Startup completed in 1738ms. Server Running: http://localhost:8080

Not 45 times, however, more than 3 times faster.

Probably, the blog guy tested maybe Linux or Mac. I tried on Windows. I’d like to try it on WSL2 as well in the near future.

Class Loader

The last challenge for this time is, class loader. I create a simple sample for dynamically load jar file and run it. Since Native Image will be a binary, so I was wondering if it is ok for dynamic class loading.

The answer is no problem.

Create Library.

LibrarySample.java

Pack it to a jar

> javac com\simplearchitect\lib\*.java
> jar cf simple-lib.jar com\simplearchitect\lib\*.class
Main.java

Run the Library from this Main.java with reflection and URLClassLoader. It works.

>java Main .\simple-lib.jar
.\simple-lib.jar
com.simplearchitect.lib.LibrarySample
Hello World from Library.

Then native image it.

>native-image Main
[main:55420] classlist: 2,074.56 ms, 0.96 GB
[main:55420] (cap): 3,979.08 ms, 0.96 GB
[main:55420] setup: 7,650.59 ms, 0.96 GB
[main:55420] (clinit): 655.34 ms, 1.70 GB
[main:55420] (typeflow): 8,206.94 ms, 1.70 GB
[main:55420] (objects): 7,481.26 ms, 1.70 GB
[main:55420] (features): 659.73 ms, 1.70 GB
[main:55420] analysis: 17,805.23 ms, 1.70 GB
[main:55420] universe: 1,561.28 ms, 1.70 GB
Warning: Reflection method java.lang.Class.getMethod invoked at Main.main(Main.java:16)
Warning: Reflection method java.lang.Class.getDeclaredConstructor invoked at Main.main(Main.java:14)
Warning: Reflection method java.lang.ClassLoader.loadClass invoked at Main.main(Main.java:13)
Warning: Aborting stand-alone image build due to reflection use without configuration.
Warning: Use -H:+ReportExceptionStackTraces to print stacktrace of underlying exception
[main:62788] classlist: 2,327.37 ms, 0.96 GB
[main:62788] (cap): 3,294.66 ms, 0.96 GB
[main:62788] setup: 6,949.62 ms, 0.96 GB
[main:62788] (clinit): 259.39 ms, 1.21 GB
[main:62788] (typeflow): 6,246.20 ms, 1.21 GB
[main:62788] (objects): 4,726.89 ms, 1.21 GB
[main:62788] (features): 295.26 ms, 1.21 GB
[main:62788] analysis: 11,872.04 ms, 1.21 GB
[main:62788] universe: 540.95 ms, 1.21 GB
[main:62788] (parse): 1,486.59 ms, 1.21 GB
[main:62788] (inline): 1,396.57 ms, 1.66 GB
[main:62788] (compile): 10,223.77 ms, 2.27 GB
[main:62788] compile: 14,036.03 ms, 2.27 GB
[main:62788] image: 1,602.32 ms, 2.28 GB
[main:62788] write: 828.06 ms, 2.28 GB
[main:62788] [total]: 38,792.06 ms, 2.28 GB
Warning: Image 'main' is a fallback image that requires a JDK for execution (use --no-fallback to suppress fallback image generation and to print more detailed information why a fallback image was necessary).

Works!

>Main .\simple-lib.jar
.\simple-lib.jar
com.simplearchitect.lib.LibrarySample
Hello World from Library.

Micronaut with WSL2 (Ubuntu18.04)

I tried Linux. It is crazy fast.

Install

Download tar.gz file from here and extract it. Then set JAVA_HOME and PATH to the JAVA_HOME/bin .

# install sdk
$ curl -s "https://get.sdkman.io" | bash
$ source "/home/ushio/.sdkman/bin/sdkman-init.sh"
$ sdk update
$ sdk install micronaut
$ mn create-app hello-world
$ cd hello-world
$ ./gradlew assemble
$ cd build/libs
$ sudo apt-get update && sudo apt-get install zlib1g-dev
$ native-image -jar hello-world-0.1-all.jar

OpenJDK 11

java -jar hello-world-0.1-all.jar 
20:39:21.481 [main] INFO io.micronaut.runtime.Micronaut - Startup completed in 1339ms. Server Running: http://localhost:8080

GraalVM Native Image

./hello-world-0.1-all 
20:38:12.601 [main] INFO io.micronaut.runtime.Micronaut - Startup completed in 78ms. Server Running: http://localhost:8080

CRAZY FAST!

Size

-rwxr-xr-x 1 ushio ushio 54926304 May 25 20:37 hello-world-0.1-all
-rw-r--r-- 1 ushio ushio 12839485 May 25 20:26 hello-world-0.1-all.jar

I’ve never tried, however it might works without Java runtime. I’d like to try next time. Multi-stage build might works well. Next time, I’d love to try.

Conclusion

GraalVM show me a very good improvement of the performance just switch to the GraalVM with Native Image. I don’t compare with OpenJDK, however, GraalVM without Native Image is already fast. The guy who tested on Mac or Linux, he said that it is 45 times faster against 3 times faster on Windows 10. However, it still fantastic outcome. I also learned that check the status of the support of the GraalVM for each frameworks.

When I tried in WSL2, it was 17 times faster! Graal should be used for Linux and container I believe.

--

--