/libgdx-graalvm-example

Advanced LibGDX demo with GraalVM native-image configuration

Primary LanguageJava

Libgdx + Graalvm native-image example

logo

Introduction

Please check my video for short introduction: https://www.youtube.com/watch?v=9eevmylv0fU&ab_channel=ByerNiX

Why?

I was curious how much Graalvm native-image could help with my java game. Main things that could be improved was:

  • File size
  • Memory consumption
  • Performance

How?

GraalVM uses SubstrateVM under the hood for native-image generation. Native image is generated as AOT (ahead of time) compiled java. There is no JIT compiler, native-image has to be compiled on the system for which is created, result is one executable file with JRE classes compiled inside. Let's go through the process.

Setup

Install GraalVM, set it as default java in environment variables, make sure you use windows x64 native tools when running GraalVM native-image on Windows (other systems were not tested in scope of this project). I've used this link when doing it first time.

Next, clone this repo, make fat-jar as normal (desktop dist). Put your jar in the graalvm-env directory and run build_native.bat

What do we have here?

desktop and core contains sample code covering:

  • graphics
  • music/sound
  • mouse/keyboard/controller input
  • box2d/box2dlights
  • freetype fonts
  • shaders
  • kryonet (without Kryo pojo serialization)

libgdx-native-image contains substitutions for Kryo, Libgdx nad Lwjgl.
graalvm-env contains GraalVM config files, assets and libs ready to run and test result application.

Troubleshooting

Native image is a little tricky to work with. There are some limitations (mainly reflections and jni) that has to be somehow addressed.

Tips and tricks:

  • When compiling new jar, run analysis first. GraalVM provides useful java agent that can be used for configuration generation (see graalvm-env/run_with_agent.bat). It scans your application at runtime trying to find all of runtime reflection/jni/java-proxy invocations, and converts it into configs. After each run- delete old config files or move them somewhere- in my tests agent did nothing if found old configs in its destination location.
  • Sometimes classes from graalvm itself are mentioned in config files generated by agent. Delete them or compilation will fail as these libs are not part of your runtime env.
  • If configs won't help, try to use --initialize-at-run-time/--initialize-at-build-time parameters but these can have their drawbacks in result.
  • If everything fails, you can write your own substituting code (check libgdx-native-image). It replaces original code when generating native-image. Very useful when hacking 3rd party libraries.

Is it worth?

I don't think so. I've compiled my own game TAnima:

tanima

to native image and I don't see any significant boost. I'd say that same app on jvm16 (graalvm is only java11 right now) performs even better. Comparing java11 with graalvm native image- native is much more stable, takes a little less memory and less GC pauses.

Disadvantages

  • compile time (TAnima is like 15 minutes)
  • complexity - changing configuration + long compile time makes it hard
  • problems with heavy reflection usage libs - Kryo in Kryonet generates new classes at runtime. It won't work with native-image without rewriting library. If you want to use it, try choosing libraries that use reflection only at build time.