graal java
最后两篇 博客文章重点讨论如何用Java编写自定义的Kubernetes控制器。 像往常一样,我正在与帖子一起编写演示:这使我可以面对实际问题,并能够详细说明问题。 在这种情况下,实现演示代码时,我必须处理其中的几个。 这篇文章专门针对其中之一:用Graal VM的本地映像AOT编译来编译不兼容的Java代码。
上下文
为了使Java Kubernetes控制器与Go-native控制器相提并论,有两个要求:
- 大小可以接受
- 在合理的时间内启动
运行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_BE
或UTF_32_LE
字符集-游戏结束。
可用的解决方案
这是一个大问题,下面是我针对各自结果尝试的几种解决方案。
添加所有字符集
Graal VM的本机映像编译具有将所有字符集添加到最终可执行文件-H:+AddAllCharsets
。 直到最近才起作用:我打开了一个问题 ,尽管稍有延迟,但已在最新版本(从20.0开始)中修复。
这种方法有两个缺点:
- 它特定于字符集的问题
- 它将所有字符集添加到最终的可执行文件中,从而使其尺寸更大
修复库
另一个麻烦的解决方案是实际修复库。 鉴于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;
}
}
-
@TargetClass
引用要更新的类 -
@Substitute
引用要更新的方法。 默认情况下,它将是与注释方法同名的方法。 批注允许值更改该默认行为。 - 因为更改的方法引用了原始类上的字段,所以该类需要提供此类引用。 为此,使用
@Alias
注释创建与原始名称相同的字段。
结论
Graal VM的AOT编译具有两个主要优点:小型可执行文件和快速启动时间。 但是要从中受益,必须将标准字节码实际编译为本机代码。 对于“ Hello World”之外的应用程序,这可能不是一件容易的事。 即使一个人的代码对AOT友好,依赖库也会出现问题。
为了解决这个问题,最直接的方法是使用Graal VM的功能之一,例如添加所有字符集的功能。 一种替代方法是修复依赖性,使其与AOT兼容。 如果无法做到这一点,Graal VM提供了一种机制,可以用自己选择的一种替换原始字节码 。 虽然可以改进文档,但它可以进行很多操作,例如删除代码或更新代码。
翻译自: https://blog.frankel.ch/coping-incompatible-code-graalvm-compilation/
graal java