/ GRAALVM, NATIVE, AOT, REFLECTION

Configuring Graal Native AOT for reflection

I’ve been following GraalVM with a lot of interest. One of the interesting areas is its ability to compile bytecode Ahead-Of-Time, and create a native image. Such images have a lot of advantages, including small size, no dependency on a JRE, etc.

However, AOT has some limitations. In particular, the native image executable cannot compile what it doesn’t know about. This post aims to describe how to configure the compilation process when code is using reflection.

Let’s start with a trivial use-case: a simple Class.forName() call. AOT compilation has no way of knowing about the class being loaded dynamically. Hence, this class won’t be included in the native image, and launching the application will fail.

The output will be akin to the following:

java.lang.ClassNotFoundException: java.awt.Button
  at java.lang.Throwable.<init>(Throwable.java:287)
  at java.lang.Exception.<init>(Exception.java:84)
  at java.lang.ReflectiveOperationException.<init>(ReflectiveOperationException.java:75)
  at java.lang.ClassNotFoundException.<init>(ClassNotFoundException.java:82)
  at com.oracle.svm.core.hub.ClassForNameSupport.forName(ClassForNameSupport.java:51)
  at com.oracle.svm.core.hub.DynamicHub.forName(DynamicHub.java:906)
  at ch.frankel.blog.graal.Reflection.classForName(Reflection.java:6)
  at ch.frankel.blog.graal.Main.main(Main.java:7)

Fortunately, it’s possible to create a JSON file to explicitly let the compiler know:

[
  {
    "name" : "java.awt.Button"
  }
]

To point the AOT compiler to the JSON file, use the -H:ReflectionConfigurationFiles flag:

native-image -H:ReflectionConfigurationFiles=graal.json -jar target/graal-stubbing-1.0-SNAPSHOT.jar

At this point, the dynamic load works as expected.

Trying to dynamically create a new instance - clazz.newInstance(), will fail as well without further configuration. The error message is quite informative: the no-parameter constructor has not been added explicitly to the native image. Let’s update the configuration file to make it work:

[
  {
    "name": "java.awt.Button",
    "methods": [
      { "name": "<init>", "parameterTypes": [] }
    ]
  }
]

The configuration syntax also offers some wildcard options:

[
  {
    "name": "java.awt.Button",
    "allDeclaredConstructors": true,
    "allPublicConstructors": true,
    "allDeclaredMethods": true,
    "allPublicMethods": true
  }
]

Unfortunately, this also fails. Calling the constructor calls the parent constructor, which in turn starts a whole method call chain:

java.lang.ClassNotFoundException: java.awt.EventQueue
  at java.lang.Throwable.<init>(Throwable.java:287)
  at java.lang.Exception.<init>(Exception.java:84)
  at java.lang.ReflectiveOperationException.<init>(ReflectiveOperationException.java:75)
  at java.lang.ClassNotFoundException.<init>(ClassNotFoundException.java:82)
  at com.oracle.svm.core.hub.ClassForNameSupport.forName(ClassForNameSupport.java:51)
  at com.oracle.svm.core.hub.DynamicHub.forName(DynamicHub.java:906)
  at sun.awt.SunToolkit.initEQ(SunToolkit.java:120)
  at sun.awt.SunToolkit.createNewAppContext(SunToolkit.java:303)
  at sun.awt.AppContext$2.run(AppContext.java:277)
  at sun.awt.AppContext$2.run(AppContext.java:266)
  at com.oracle.svm.core.jdk.Target_java_security_AccessController.doPrivileged(SecuritySubstitutions.java:70)
  at sun.awt.AppContext.initMainAppContext(AppContext.java:266)
  at sun.awt.AppContext.access$400(AppContext.java:135)
  at sun.awt.AppContext$3.run(AppContext.java:321)
  at sun.awt.AppContext$3.run(AppContext.java:304)
  at com.oracle.svm.core.jdk.Target_java_security_AccessController.doPrivileged(SecuritySubstitutions.java:70)
  at sun.awt.AppContext.getAppContext(AppContext.java:303)
  at java.awt.Component.<init>(Component.java:988)
  at java.awt.Button.<init>(Button.java:151)

Looking at the source code reveals another reflection call.

sun.awt.SunToolkit
public abstract class SunToolkit extends Toolkit {

  private static void initEQ(AppContext var0) {
    String var2 = System.getProperty("AWT.EventQueueClass", "java.awt.EventQueue");
    EventQueue var1;
    try {
      var1 = (EventQueue)Class.forName(var2).newInstance(); (1)
    } catch (Exception var4) {
      var4.printStackTrace();
      System.err.println("Failed loading " + var2 + ": " + var4);
      var1 = new EventQueue();
    }
    var0.put(AppContext.EVENT_QUEUE_KEY, var1);
    PostEventQueue var3 = new PostEventQueue(var1);
    var0.put("PostEventQueue", var3);
  }
}
1Aye, there’s the rub!

The configuration needs to take that additional reflective call into account. The fixed file is the following:

[
  {
    "name": "java.awt.Button",
    "methods": [
      { "name": "<init>", "parameterTypes": [] }
    ]
  },
  {
    "name": "java.awt.EventQueue",
    "methods": [
      {  "name": "<init>", "parameterTypes": [] }
    ]
  }
]

The next step is to directly access a field through reflection: clazz.getDeclaredField("label").

The corresponding configuration file becomes:

[
  {
    "name": "java.awt.Button",
    "methods": [
      { "name": "<init>", "parameterTypes": [] }
    ],
    "fields": [
      { "name": "label" }
    ]
  },
  {
    "name": "java.awt.EventQueue",
    "methods": [
      { "name": "<init>", "parameterTypes": [] }
    ]
  }
]

A recent commit named "Intrinsify reflective calls when the argument is a constant" will improve reflection auto-configuration:

If the arguments to these calls can be reduced to a constant we try to resolve the target elements. If the target elements can be resolved the calls are removed and instead the target elements are embedded in the code.

At its most basic level, constants will be resolved automatically, with no need to create a configuration file, e.g.:

Class<?> clazz = Class.forName("java.awt.Button");

Conclusion

If your application uses reflection, the AOT compiler needs a configuration file to be explicitly told about dynamic loading/access. While the process itself is quite straightforward, the different method call chains need to be followed until the end. Depending on the application, it can be quite tedious and time-consuming.

The complete source code for this post can be found on Github in Maven format.
Nicolas Fränkel

Nicolas Fränkel

Nicolas Fränkel is a Developer Advocate with 15+ years experience consulting for many different customers, in a wide range of contexts (such as telecoms, banking, insurances, large retail and public sector). Usually working on Java/Java EE and Spring technologies, but with narrower interests like Software Quality, Build Processes and Rich Internet Applications. Currently working for Exoscale. Also double as a teacher in universities and higher education schools, a trainer and triples as a book author.

Read More