配置Graal本机AOT进行反射

我一直很感兴趣地关注 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)

查看源代码揭示了另一个反射调用。

sun.awt.SunToolkit
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);
  }
}
  1. 是的,有摩擦!

该配置需要考虑该额外的反射性调用。 固定文件如下:

[
  {
    "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编译器需要一个配置文件来明确告知有关动态加载/访问的信息。 尽管过程本身非常简单,但是需要遵循不同的方法调用链,直到最后。 根据应用程序的不同,它可能非常繁琐且耗时。

可以在Github上以Maven格式找到此文章的完整源代码。

翻译自: https://blog.frankel.ch/configuring-graal-native-aot-reflection/

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值