我一直很感兴趣地关注 GraalVM。 有趣的领域之一是它能够提前编译字节码并创建本机映像。 这样的图像具有很多优点,包括尺寸小,不依赖JRE等。
但是,AOT有一些限制。 特别是,本机映像可执行文件无法编译不知道的内容。 这篇文章旨在描述当代码使用反射时如何配置编译过程。
让我们从一个简单的用例开始:一个简单的Class.forName()
调用。 AOT编译无法知道动态加载的类。 因此,此类不会包含在本机映像中,并且启动应用程序将失败。
输出将类似于以下内容:
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)
幸运的是,可以创建一个JSON文件来明确让编译器知道:
[
{
"name" : "java.awt.Button"
}
]
要将AOT编译器指向JSON文件,请使用-H:ReflectionConfigurationFiles
标志:
native-image-H :ReflectionConfigurationFiles = graal.json -jar target/graal-stubbing-1.0-SNAPSHOT.jar
此时,动态负载将按预期工作。
如果没有进一步的配置,尝试动态创建新实例clazz.newInstance()
也会失败。 该错误消息非常有用: the no-parameter constructor has not been added explicitly to the native image
。 让我们更新配置文件以使其工作:
[
{
"name": "java.awt.Button",
"methods": [
{ "name": "<init>", "parameterTypes": [] }
]
}
]
配置语法还提供了一些通配符选项:[{“名称”:“ java.awt.Button”,“ allDeclaredConstructors”:true,“ allPublicConstructors”:true,“ allDeclaredMethods”:true,“ allPublicMethods”:true}]
不幸的是,这也失败了。 调用构造函数将调用父构造函数,这将启动整个方法调用链:
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)
查看源代码揭示了另一个反射调用。
publicabstractclassSunToolkitextendsToolkit{
privatestaticvoidinitEQ(AppContextvar0){
Stringvar2=System.getProperty("AWT.EventQueueClass","java.awt.EventQueue");
EventQueuevar1;
try{
var1=(EventQueue)Class.forName(var2).newInstance(); (1)
}catch(Exceptionvar4){
var4.printStackTrace();
System.err.println("Failed loading "+var2+": "+var4);
var1=newEventQueue();
}
var0.put(AppContext.EVENT_QUEUE_KEY,var1);
PostEventQueuevar3=newPostEventQueue(var1);
var0.put("PostEventQueue",var3);
}
}
- 是的,有摩擦!
该配置需要考虑该额外的反射性调用。 固定文件如下:
[
{
"name": "java.awt.Button",
"methods": [
{ "name": "<init>", "parameterTypes": [] }
]
},
{
"name": "java.awt.EventQueue",
"methods": [
{ "name": "<init>", "parameterTypes": [] }
]
}
]
下一步是通过反射直接访问字段: clazz.getDeclaredField("label")
。
相应的配置文件变为:
[
{
"name": "java.awt.Button",
"methods": [
{ "name": "<init>", "parameterTypes": [] }
],
"fields": [
{ "name": "label" }
]
},
{
"name": "java.awt.EventQueue",
"methods": [
{ "name": "<init>", "parameterTypes": [] }
]
}
]
最近的一次提交名为“当参数为常数时,对反射调用进行Intrinsify反射调用”将改善反射自动配置:如果可以将这些调用的参数简化为常量,我们将尝试解析目标元素。 如果可以解析目标元素,那么将删除调用,而是将目标元素嵌入到代码中。 在最基本的级别上,常量将自动解析,无需创建配置文件,例如:Class <?> clazz = Class.forName(“ java.awt.Button”);
结论
如果您的应用程序使用反射,则AOT编译器需要一个配置文件来明确告知有关动态加载/访问的信息。 尽管过程本身非常简单,但是需要遵循不同的方法调用链,直到最后。 根据应用程序的不同,它可能非常繁琐且耗时。
翻译自: https://blog.frankel.ch/configuring-graal-native-aot-reflection/