graal java_应对Graal VM AOT编译中的不兼容代码

graal java

最后两篇 博客文章重点讨论如何用Java编写自定义的Kubernetes控制器。 像往常一样,我正在与帖子一起编写演示:这使我可以面对实际问题,并能够详细说明问题。 在这种情况下,实现演示代码时,我必须处理其中的几个。 这篇文章专门针对其中之一:用Graal VM的本地映像AOT编译来编译不兼容的Java代码。

上下文

为了使Java Kubernetes控制器与Go-native控制器相提并论,有两个要求:

  1. 大小可以接受
  2. 在合理的时间内启动

运行JVM应用程序的Docker映像嵌入了JVM本身,因此无法满足第一个要求。 此外,众所周知JVM平台以长时间运行的进程提供出色的性能而闻名...以更长的启动时间为代价。 为了能够满足这些要求,一种选择是使用Java 11平台和模块来生成不依赖于平台的独立可执行文件。 另一种选择是使用Graal VM的Ahead-of-Time编译功能来生成本机可执行文件。 第二个选项是我选择的。

Graal VM的AOT编译概念很容易掌握。 在构建时,本native-image可执行文件运行从main入口点开始的所有执行路径。 与往常一样,这对于“ Hello World”应用程序来说就像轻轻松松。 不幸的是,控制器要比这复杂一些。

问题

特别是,它需要一种与Kubernetes API服务器进行交互的方法(请参阅Kubernetes体系结构的介绍 )。 对于演示,我正在使用Fabric8的Kubernetes Client 。 命令行如下:

native-image-jar /var/jvm-operator.jar -H :+ReportExceptionStackTraces

创建了Docker镜像后,我将Pod安排在Kubernetes上。 这将记录以下错误堆栈跟踪:

Exception in thread "main" java.lang.ExceptionInInitializerError
	at com.oracle.svm.core.hub.ClassInitializationInfo.initialize(ClassInitializationInfo.java:290)
	at java.lang.Class.ensureInitialized(DynamicHub.java:467)
	at okhttp3.OkHttpClient.<clinit>(OkHttpClient.java:127)
	at com.oracle.svm.core.hub.ClassInitializationInfo.invokeClassInitializer(ClassInitializationInfo.java:350)
	at com.oracle.svm.core.hub.ClassInitializationInfo.initialize(ClassInitializationInfo.java:270)
	at java.lang.Class.ensureInitialized(DynamicHub.java:467)
	at okhttp3.OkHttpClient$Builder.<init>(OkHttpClient.java:475)
	at io.fabric8.kubernetes.client.utils.HttpClientUtils.createHttpClient(HttpClientUtils.java:73)
	at io.fabric8.kubernetes.client.utils.HttpClientUtils.createHttpClient(HttpClientUtils.java:64)
	at io.fabric8.kubernetes.client.BaseClient.<init>(BaseClient.java:51)
	at io.fabric8.kubernetes.client.BaseClient.<init>(BaseClient.java:43)
	at io.fabric8.kubernetes.client.DefaultKubernetesClient.<init>(DefaultKubernetesClient.java:89)
	at ch.frankel.kubernetes.extend.Sidecar.main(Sidecar.java:13)
Caused by: java.nio.charset.UnsupportedCharsetException: UTF-32BE
	at java.nio.charset.Charset.forName(Charset.java:531)
	at okhttp3.internal.Util.<clinit>(Util.java:75)
	at com.oracle.svm.core.hub.ClassInitializationInfo.invokeClassInitializer(ClassInitializationInfo.java:350)
	at com.oracle.svm.core.hub.ClassInitializationInfo.initialize(ClassInitializationInfo.java:270)

从堆栈跟踪可以看出,问题来自Square的OkHttp ,它是Kubernetes Client的依赖项。 有罪代码如下(摘要):

publicfinalclassUtil{

  privatestaticfinalByteStringUTF_8_BOM=ByteString.decodeHex("efbbbf");
  privatestaticfinalByteStringUTF_16_BE_BOM=ByteString.decodeHex("feff");
  privatestaticfinalByteStringUTF_16_LE_BOM=ByteString.decodeHex("fffe");
  privatestaticfinalByteStringUTF_32_BE_BOM=ByteString.decodeHex("0000ffff");
  privatestaticfinalByteStringUTF_32_LE_BOM=ByteString.decodeHex("ffff0000");

  publicstaticfinalCharsetUTF_8=Charset.forName("UTF-8");
  publicstaticfinalCharsetISO_8859_1=Charset.forName("ISO-8859-1");
  privatestaticfinalCharsetUTF_16_BE=Charset.forName("UTF-16BE");
  privatestaticfinalCharsetUTF_16_LE=Charset.forName("UTF-16LE");
  privatestaticfinalCharsetUTF_32_BE=Charset.forName("UTF-32BE");
  privatestaticfinalCharsetUTF_32_LE=Charset.forName("UTF-32LE");

  publicstaticCharsetbomAwareCharset(BufferedSourcesource,Charsetcharset)throwsIOException{
    if(source.rangeEquals(0,UTF_8_BOM)){
      source.skip(UTF_8_BOM.size());
      returnUTF_8;
    }
    if(source.rangeEquals(0,UTF_16_BE_BOM)){
      source.skip(UTF_16_BE_BOM.size());
      returnUTF_16_BE;
    }
    if(source.rangeEquals(0,UTF_16_LE_BOM)){
      source.skip(UTF_16_LE_BOM.size());
      returnUTF_16_LE;
    }
    if(source.rangeEquals(0,UTF_32_BE_BOM)){
      source.skip(UTF_32_BE_BOM.size());
      returnUTF_32_BE;
    }
    if(source.rangeEquals(0,UTF_32_LE_BOM)){
      source.skip(UTF_32_LE_BOM.size());
      returnUTF_32_LE;
    }
    returncharset;
  }
}

初始化在运行时进行:当类“加载”时-没有使用本机可执行文件进行真正的类加载-每个Charset常量都运行各自的Charset.forName() 。 在我运行的系统上,没有UTF_32_BEUTF_32_LE字符集-游戏结束。

可用的解决方案

这是一个大问题,下面是我针对各自结果尝试的几种解决方案。

添加所有字符集

Graal VM的本机映像编译具有将所有字符集添加到最终可执行文件-H:+AddAllCharsets 。 直到最近才起作用:我打开了一个问题 ,尽管稍有延迟,但已在最新版本(从20.0开始)中修复。

这种方法有两个缺点:

  1. 它特定于字符集的问题
  2. 它将所有字符集添加到最终的可执行文件中,从而使其尺寸更大

修复库

另一个麻烦的解决方案是实际修复库。 鉴于OkHttp是开源的,其源代码可在GitHub上获得 。 删除有罪代码,构建固定版本并在Maven POM中添加新工件以替换原始工件,这很容易。

Graal VM将此解决方案宣传为首选解决方案。

换人

但是,有时候源代码不可用,或者工件也不容易更改。 因此,Graal VM提供了一种替代机制。 在AOT编译时,它可以通过替换或删除特定的字节码来进行更改。

第一步是将Substrate VM JAR添加到类路径

<dependency>
  <groupId> com.oracle.substratevm </groupId>
  <artifactId> svm </artifactId>
  <version> 19.2.1 </version>
  <scope> provided </scope>
</dependency>

注意,提供了作用域,以便不将其包括在最终工件中。

在撰写本文时,有关替代的文档非常少。 接下来是我在开发过程中附带的内容,可能包含一些错误,或者与您使用它时实际实现的内容完全不同。

替代语言是用Java开发的-尽管我推断可以用任何产生Java兼容字节码的语言编写它们。 有两种方法可以配置代码替换。

使用JSON配置文件

在网上找到配置文件示例时 ,我没有找到有关预期文件格式的文档。 运行native-image时,需要在命令行上引用这些文件。

-H:SubstitutionFiles=...  Comma-separated list of file names with declarative substitutions. Default: None
-H:SubstitutionResources  Comma-separated list of resource file names with declarative substitutions. Default: None
带注释

com.oracle.svm.core.annotate包中可以找到注释。

主要的是TargetClass来引用需要更新的原始类,以及@Substitute来实际更新代码本身。

这是解决上述问题的代码:

@TargetClass(Util.class) (1)
publicfinalclassokhttp3_internal_Util{

    @Alias (3)
    privatestaticByteStringUTF_8_BOM;
    @Alias (3)
    privatestaticByteStringUTF_16_BE_BOM;
    @Alias (3)
    privatestaticByteStringUTF_16_LE_BOM;
    @Alias (3)
    publicstaticCharsetUTF_8;
    @Alias (3)
    privatestaticCharsetUTF_16_BE;
    @Alias (3)
    privatestaticCharsetUTF_16_LE;

    @Substitute (2)
    publicstaticCharsetbomAwareCharset(BufferedSourcesource,Charsetcharset)throwsIOException{
        if(source.rangeEquals(0,UTF_8_BOM)){
            source.skip(UTF_8_BOM.size());
            returnUTF_8;
        }
        if(source.rangeEquals(0,UTF_16_BE_BOM)){
            source.skip(UTF_16_BE_BOM.size());
            returnUTF_16_BE;
        }
        if(source.rangeEquals(0,UTF_16_LE_BOM)){
            source.skip(UTF_16_LE_BOM.size());
            returnUTF_16_LE;
        }
        returncharset;
    }
}
  1. @TargetClass引用要更新的类
  2. @Substitute引用要更新的方法。 默认情况下,它将是与注释方法同名的方法。 批注允许值更改该默认行为。
  3. 因为更改的方法引用了原始类上的字段,所以该类需要提供此类引用。 为此,使用@Alias注释创建与原始名称相同的字段。

结论

Graal VM的AOT编译具有两个主要优点:小型可执行文件和快速启动时间。 但是要从中受益,必须将标准字节码实际编译为本机代码。 对于“ Hello World”之外的应用程序,这可能不是一件容易的事。 即使一个人的代码对AOT友好,依赖库也会出现问题。

为了解决这个问题,最直接的方法是使用Graal VM的功能之一,例如添加所有字符集的功能。 一种替代方法是修复依赖性,使其与AOT兼容。 如果无法做到这一点,Graal VM提供了一种机制,可以用自己选择的一种替换原始字节码 。 虽然可以改进文档,但它可以进行很多操作,例如删除代码或更新代码。

翻译自: https://blog.frankel.ch/coping-incompatible-code-graalvm-compilation/

graal java

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值