Rom*_*kiy 8 java netty graalvm-native-image
我有一个使用 Micronaut 2.0.0 实现 RESTful API 的小 Java 应用程序。在幕后,它使用 Redisson 3.13.1 转到 Redis。Redisson 反过来使用 Netty (4.1.49)。
该应用程序在“经典”java(在 HotSpot 上,Java 8 和 11)中运行良好。
我正在尝试使用 GraalVM 从此应用程序构建本机映像。
命令大概是这样的:
native-image --no-server --no-fallback -H:+TraceClassInitialization -H:+PrintClassInitialization --report-unsupported-elements-at-runtime --initialize-at-build-time=reactor.core.publisher.Flux,reactor.core.publisher.Mono -H:ConfigurationFileDirectories=target/config -cp target/app-1.0.0-SNAPSHOT.jar com.app.AppApplication target/app
Run Code Online (Sandbox Code Playgroud)
这是我得到的:
Error: Unsupported features in 4 methods
Detailed message:
Error: No instances of java.net.Inet4Address are allowed in the image heap as this class should be initialized at image runtime. Object has been initialized without the native-image initialization instrumentation and the stack trace can't be tracked.
Trace: Object was reached by
reading field io.netty.channel.socket.InternetProtocolFamily.localHost of
constant io.netty.channel.socket.InternetProtocolFamily@593f1f62 reached by
scanning method io.netty.resolver.dns.DnsNameResolver.preferredAddressType(DnsNameResolver.java:481)
Call path from entry point to io.netty.resolver.dns.DnsNameResolver.preferredAddressType(ResolvedAddressTypes):
at io.netty.resolver.dns.DnsNameResolver.preferredAddressType(DnsNameResolver.java:478)
at io.netty.resolver.dns.DnsNameResolver.<init>(DnsNameResolver.java:436)
at io.netty.resolver.dns.DnsNameResolverBuilder.build(DnsNameResolverBuilder.java:473)
at io.netty.resolver.dns.DnsAddressResolverGroup.newNameResolver(DnsAddressResolverGroup.java:111)
at io.netty.resolver.dns.DnsAddressResolverGroup.newResolver(DnsAddressResolverGroup.java:91)
at io.netty.resolver.dns.DnsAddressResolverGroup.newResolver(DnsAddressResolverGroup.java:76)
at io.netty.resolver.AddressResolverGroup.getResolver(AddressResolverGroup.java:70)
at org.redisson.cluster.ClusterConnectionManager$1.run(ClusterConnectionManager.java:251)
at com.oracle.svm.core.jdk.RuntimeSupport.executeHooks(RuntimeSupport.java:125)
at com.oracle.svm.core.jdk.RuntimeSupport.executeStartupHooks(RuntimeSupport.java:75)
at com.oracle.svm.core.JavaMainWrapper.runCore(JavaMainWrapper.java:141)
at com.oracle.svm.core.JavaMainWrapper.run(JavaMainWrapper.java:184)
at com.oracle.svm.core.code.IsolateEnterStub.JavaMainWrapper_run_5087f5482cc9a6abc971913ece43acb471d2631b(generated:0)
Run Code Online (Sandbox Code Playgroud)
这只是输出的一部分,它还对其他 3 个错误产生类似的报告。
我仍在努力理解这个问题,但我想,正如其中的java.net.InetAddress本机方法一样,它及其子类java.net.Inet4Address都不能在构建时初始化。这意味着Inet4Address对于在构建时(在初始化阶段,在 Java 术语中)初始化的代码, 的实例是不可见的。并且本机图像构建器找到了一种方法,可以达到这样一个对象可见的程度。它甚至可以显示的痕迹,但问题是,ClusterConnectionManager$1是Runnable该只提交到Executor在运行时(静态初始化后waaaay)。
你如何调试这种情况?即:
附注。如果我添加--initialize-at-run-time=java.net.InetAddress,它会以不同的方式失败:
Error: The class java.net.InetAddress has already been initialized; it is too late
to register java.net.InetAddress for build-time initialization (from the command
line). java.net.InetAddress has been initialized without the native-image
initialization instrumentation and the stack trace can't be tracked. Try avoiding
this conflict by avoiding to initialize the class that caused initialization of
java.net.InetAddress or by not marking java.net.InetAddress for build-time
initialization.
Run Code Online (Sandbox Code Playgroud)
Java 将自身报告为build 25.252-b09-jvmci-20.1-b02, mixed mode.
聚苯乙烯。我发现图像堆中不允许...的实例,因为此类应在图像运行时初始化,并且似乎 Quarkus 问题已修复。但我仍然不明白如何解决手头的问题。任何帮助,将不胜感激。
Rom*_*kiy 11
TLDR;答案末尾有一小部分带有摘要。
在 Java 中,每个类都必须在使用前进行初始化。初始化意味着执行静态字段初始化程序和静态初始化块。在标准 JVM(如 HotSpot)中,这当然发生在运行时。
但是对于本机映像,您有两种选择。一个类可能仍然在运行时被初始化,或者它的初始化可能在构建时被执行。后者有一个明显的好处是避免在原生镜像启动时进行这项工作,这使得镜像启动更快。但是对于某些类,在构建时初始化它们是没有意义的。这样的例子可以是一个类,它根据环境(env 变量、配置文件等)在其初始化(例如,创建这个或那个类的实例)中做出一些决定。
在构建/运行时初始化替代方案之间进行选择有一些限制:
从 19.3.0 版开始,native-image工具要求java.net.InetAddress该类始终在运行时初始化。这(通过限制 2)意味着它的子类,java.net.Inet4Address并且java.net.Inet6Address还必须在运行时初始化,这反过来(通过限制 4)意味着您不能InetAddress被构建时初始化的类引用。
所有我们在这里遇到构建失败是由这个相同的问题引起的:要么Inet4Address或Inet6Address
图像堆。
原来netty-codec-http包含以下内容
Args = --initialize-at-build-time=io.netty \
Run Code Online (Sandbox Code Playgroud)
在其native-image.properties下面META-INF,并且 micronautnetty-codec-http作为依赖项,因此io.netty默认情况下所有
类都在构建时初始化(因为native-image工具尊重此类
native-image.properties文件)。
这里https://github.com/rpuch/netty-InetAddress-native-image-diagnosing是一个对问题建模的项目,我进一步使用它来展示如何解决问题。其main()方法如下:
public static void main(String[] args) throws Exception {
NioEventLoopGroup group = new NioEventLoopGroup(1, new DefaultThreadFactory("netty"));
DnsAddressResolverGroup resolverGroup = new DnsAddressResolverGroup(NioDatagramChannel.class,
DnsServerAddressStreamProviders.platformDefault());
AddressResolver<InetSocketAddress> resolver = resolverGroup.getResolver(group.next());
System.out.println(resolver);
resolver.close();
group.shutdownGracefully().get();
}
Run Code Online (Sandbox Code Playgroud)
它会产生与以下代码相同的效果(关于 Netty):
Config config = new Config();
config.useSingleServer().setAddress(redisUri);
config.useSingleServer().setPassword(redisPassword);
return Redisson.createReactive(config);
Run Code Online (Sandbox Code Playgroud)
该项目--initialize-at-build-time=io.netty在其构建脚本中也有模拟基于 micronaut 的项目行为。
因此,它是使这个问题曝光的原始项目的有用替代品。
我在这里使用的是 20.2.0 版(截至撰写本文时最新发布的版本)。
构建失败并出现以下错误:
Error: com.oracle.graal.pointsto.constraints.UnsupportedFeatureException: No instances of java.net.Inet4Address are allowed in the image heap as this class should be initialized at image runtime. Object has been initialized without the native-image initialization instrumentation and the stack trace can't be tracked.
Trace:
at parsing io.netty.resolver.dns.DnsNameResolver.resolveHostsFileEntry(DnsNameResolver.java:659)
Call path from entry point to io.netty.resolver.dns.DnsNameResolver.resolveHostsFileEntry(String):
at io.netty.resolver.dns.DnsNameResolver.resolveHostsFileEntry(DnsNameResolver.java:651)
at io.netty.resolver.dns.DnsNameResolver.doResolve(DnsNameResolver.java:884)
at io.netty.resolver.dns.DnsNameResolver.doResolve(DnsNameResolver.java:733)
at io.netty.resolver.SimpleNameResolver.resolve(SimpleNameResolver.java:61)
at io.netty.resolver.SimpleNameResolver.resolve(SimpleNameResolver.java:53)
at io.netty.resolver.InetSocketAddressResolver.doResolve(InetSocketAddressResolver.java:55)
at io.netty.resolver.InetSocketAddressResolver.doResolve(InetSocketAddressResolver.java:31)
at io.netty.resolver.AbstractAddressResolver.resolve(AbstractAddressResolver.java:106)
at io.netty.bootstrap.Bootstrap.doResolveAndConnect0(Bootstrap.java:206)
at io.netty.bootstrap.Bootstrap.access$000(Bootstrap.java:46)
at io.netty.bootstrap.Bootstrap$1.operationComplete(Bootstrap.java:180)
Run Code Online (Sandbox Code Playgroud)
DnsNameResolver:659 是
return LOCALHOST_ADDRESS;
Run Code Online (Sandbox Code Playgroud)
并且它引用名为LOCALHOST_ADDRESStype的静态字段InetAddress。让我们通过在命令中添加以下内容来避免在构建时初始化native-image:
--initialize-at-run-time=io.netty.resolver.dns.DnsNameResolver
Run Code Online (Sandbox Code Playgroud)
错误消失了。
现在还有一个:
Error: No instances of java.net.Inet6Address are allowed in the image heap as this class should be initialized at image runtime. Object has been initialized without the native-image initialization instrumentation and the stack trace can't be tracked.
Trace: Object was reached by
reading field java.util.HashMap$Node.value of
constant java.util.HashMap$Node@26eb0f30 reached by
indexing into array
constant java.util.HashMap$Node[]@63e95621 reached by
reading field java.util.HashMap.table of
constant java.util.HashMap@563992d1 reached by
reading field java.util.Collections$UnmodifiableMap.m of
constant java.util.Collections$UnmodifiableMap@38a9945c reached by
reading field io.netty.resolver.DefaultHostsFileEntriesResolver.inet6Entries of
constant io.netty.resolver.DefaultHostsFileEntriesResolver@7ef4ba7e reached by
scanning method io.netty.resolver.dns.DnsNameResolverBuilder.<init>(DnsNameResolverBuilder.java:56)
Call path from entry point to io.netty.resolver.dns.DnsNameResolverBuilder.<init>():
at io.netty.resolver.dns.DnsNameResolverBuilder.<init>(DnsNameResolverBuilder.java:68)
at io.netty.resolver.dns.DnsAddressResolverGroup.<init>(DnsAddressResolverGroup.java:54)
at Main.main(Main.java:18)
Run Code Online (Sandbox Code Playgroud)
DnsNameResolverBuilder:56 是
private HostsFileEntriesResolver hostsFileEntriesResolver = HostsFileEntriesResolver.DEFAULT;
Run Code Online (Sandbox Code Playgroud)
让我们推迟HostsFileEntriesResolver初始化:
--initialize-at-run-time=io.netty.resolver.HostsFileEntriesResolver
Run Code Online (Sandbox Code Playgroud)
现在,还有另一个错误:
Error: com.oracle.graal.pointsto.constraints.UnsupportedFeatureException: No instances of java.net.Inet6Address are allowed in the image heap as this class should be initialized at image runtime. Object has been initialized without the native-image initialization instrumentation and the stack trace can't be tracked.
Trace:
at parsing io.netty.resolver.dns.DnsQueryContextManager.getOrCreateContextMap(DnsQueryContextManager.java:111)
Call path from entry point to io.netty.resolver.dns.DnsQueryContextManager.getOrCreateContextMap(InetSocketAddress):
at io.netty.resolver.dns.DnsQueryContextManager.getOrCreateContextMap(DnsQueryContextManager.java:96)
Run Code Online (Sandbox Code Playgroud)
DnsQueryContextManager:111参考资料NetUtil.LOCALHOST6。NetUtil在构建时被初始化,但其字段
LOCALHOST4和LOCALHOST6包含的实例Inet4Address和Inet6Address分别。这可以通过替换来解决。我们只需将以下类添加到我们的项目中:
@TargetClass(NetUtil.class)
final class NetUtilSubstitutions {
@Alias
@InjectAccessors(NetUtilLocalhost4Accessor.class)
public static Inet4Address LOCALHOST4;
@Alias
@InjectAccessors(NetUtilLocalhost6Accessor.class)
public static Inet6Address LOCALHOST6;
private static class NetUtilLocalhost4Accessor {
static Inet4Address get() {
// using https://en.wikipedia.org/wiki/Initialization-on-demand_holder_idiom
return NetUtilLocalhost4LazyHolder.LOCALHOST4;
}
static void set(Inet4Address ignored) {
// a no-op setter to avoid exceptions when NetUtil is initialized at run-time
}
}
private static class NetUtilLocalhost4LazyHolder {
private static final Inet4Address LOCALHOST4;
static {
byte[] LOCALHOST4_BYTES = {127, 0, 0, 1};
// Create IPv4 loopback address.
try {
LOCALHOST4 = (Inet4Address) InetAddress.getByAddress("localhost", LOCALHOST4_BYTES);
} catch (Exception e) {
// We should not get here as long as the length of the address is correct.
PlatformDependent.throwException(e);
throw new IllegalStateException("Should not reach here");
}
}
}
private static class NetUtilLocalhost6Accessor {
static Inet6Address get() {
// using https://en.wikipedia.org/wiki/Initialization-on-demand_holder_idiom
return NetUtilLocalhost6LazyHolder.LOCALHOST6;
}
static void set(Inet6Address ignored) {
// a no-op setter to avoid exceptions when NetUtil is initialized at run-time
}
}
private static class NetUtilLocalhost6LazyHolder {
private static final Inet6Address LOCALHOST6;
static {
byte[] LOCALHOST6_BYTES = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1};
// Create IPv6 loopback address.
try {
LOCALHOST6 = (Inet6Address) InetAddress.getByAddress("localhost", LOCALHOST6_BYTES);
} catch (Exception e) {
// We should not get here as long as the length of the address is correct.
PlatformDependent.throwException(e);
throw new IllegalStateException("Should not reach here");
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
这个想法是用我们控制的方法调用替换有问题的字段的负载。这是通过替换(注意@TargetClass,@Alias和@InjectAccessors)实现的。结果,这些InetAddress值不再存储在图像堆中。错误消失了。
我们现在有另一个:
Error: No instances of java.net.Inet6Address are allowed in the image heap as this class should be initialized at image runtime. Object has been initialized without the native-image initialization instrumentation and the stack trace can't be tracked.
Trace: Object was reached by
reading field io.netty.channel.socket.InternetProtocolFamily.localHost of
constant io.netty.channel.socket.InternetProtocolFamily@5dc39065 reached by
scanning method io.netty.resolver.dns.DnsNameResolver.preferredAddressType(DnsNameResolver.java:487)
Call path from entry point to io.netty.resolver.dns.DnsNameResolver.preferredAddressType(ResolvedAddressTypes):
at io.netty.resolver.dns.DnsNameResolver.preferredAddressType(DnsNameResolver.java:481)
Run Code Online (Sandbox Code Playgroud)
从 的代码中可以看出InternetProtocolFamily,每个枚举常量都存储了一个 的实例InetAddress,因此如果任何在构建时初始化的类 初始化InternetProtocolFamily,图像堆就会被InetAddress实例污染
。这也可以通过替换来解决:
@TargetClass(InternetProtocolFamily.class)
final class InternetProtocolFamilySubstitutions {
@Alias
@InjectAccessors(InternetProtocolFamilyLocalhostAccessor.class)
private InetAddress localHost;
private static class InternetProtocolFamilyLocalhostAccessor {
static InetAddress get(InternetProtocolFamily family) {
switch (family) {
case IPv4:
return NetUtil.LOCALHOST4;
case IPv6:
return NetUtil.LOCALHOST6;
default:
throw new IllegalStateException("Unsupported internet protocol family: " + family);
}
}
static void set(InternetProtocolFamily family, InetAddress address) {
// storing nothing as the getter derives all it needs from its argument
}
}
}
Run Code Online (Sandbox Code Playgroud)
错误消失了。
这次还有一个:
Error: No instances of java.net.Inet4Address are allowed in the image heap as this class should be initialized at image runtime. Object has been initialized without the native-image initialization instrumentation and the stack trace can't be tracked.
Detailed message:
Trace: Object was reached by
reading field java.net.InetSocketAddress$InetSocketAddressHolder.addr of
constant java.net.InetSocketAddress$InetSocketAddressHolder@34913c36 reached by
reading field java.net.InetSocketAddress.holder of
constant java.net.InetSocketAddress@ad1fe10 reached by
reading field io.netty.resolver.dns.SingletonDnsServerAddresses.address of
constant io.netty.resolver.dns.SingletonDnsServerAddresses@79fd599 reached by
scanning method io.netty.resolver.dns.DefaultDnsServerAddressStreamProvider.nameServerAddressStream(DefaultDnsServerAddressStreamProvider.java:115)
Call path from entry point to io.netty.resolver.dns.DefaultDnsServerAddressStreamProvider.nameServerAddressStream(String):
at io.netty.resolver.dns.DefaultDnsServerAddressStreamProvider.nameServerAddressStream(DefaultDnsServerAddressStreamProvider.java:115)
at io.netty.resolver.dns.DnsServerAddressStreamProviders$DefaultProviderHolder$1.nameServerAddressStream(DnsServerAddressStreamProviders.java:131)
at io.netty.resolver.dns.DnsNameResolver.doResolveAllUncached0(DnsNameResolver.java:1070)
Run Code Online (Sandbox Code Playgroud)
首先,让我们将初始化移动io.netty.resolver.dns.DefaultDnsServerAddressStreamProvider到运行时:
--initialize-at-run-time=io.netty.resolver.dns.DefaultDnsServerAddressStreamProvider
Run Code Online (Sandbox Code Playgroud)
现在,错误是相似的,但仍然略有不同:
Error: No instances of java.net.Inet4Address are allowed in the image heap as this class should be initialized at image runtime. Object has been initialized without the native-image initialization instrumentation and the stack trace can't be tracked.
Trace: Object was reached by
reading field java.net.InetSocketAddress$InetSocketAddressHolder.addr of
constant java.net.InetSocketAddress$InetSocketAddressHolder@5537c5de reached by
reading field java.net.InetSocketAddress.holder of
constant java.net.InetSocketAddress@fb954f8 reached by
reading field io.netty.resolver.dns.SingletonDnsServerAddresses.address of
constant io.netty.resolver.dns.SingletonDnsServerAddresses@3ec9baab reached by
reading field io.netty.resolver.dns.UnixResolverDnsServerAddressStreamProvider.defaultNameServerAddresses of
constant io.netty.resolver.dns.UnixResolverDnsServerAddressStreamProvider@1b7f0339 reached by
reading field io.netty.resolver.dns.DnsServerAddressStreamProviders$DefaultProviderHolder$1.currentProvider of
constant io.netty.resolver.dns.DnsServerAddressStreamProviders$DefaultProviderHolder$1@2d249be7 reached by
scanning method io.netty.resolver.dns.DnsServerAddressStreamProviders.unixDefault(DnsServerAddressStreamProviders.java:104)
Call path from entry point to io.netty.resolver.dns.DnsServerAddressStreamProviders.unixDefault():
at io.netty.resolver.dns.DnsServerAddressStreamProviders.unixDefault(DnsServerAddressStreamProviders.java:104)
at io.netty.resolver.dns.DnsServerAddressStreamProviders.platformDefault(DnsServerAddressStreamProviders.java:100)
at Main.main(Main.java:18)
Run Code Online (Sandbox Code Playgroud)
好的,让我们也将初始化移动io.netty.resolver.dns.DnsServerAddressStreamProviders$DefaultProviderHolder到运行时:
'--initialize-at-run-time=io.netty.resolver.dns.DnsServerAddressStreamProviders$DefaultProviderHolder'
Run Code Online (Sandbox Code Playgroud)
(注意单引号:没有它们$和后面的字符将被解释sh为空字符串并替换为空字符串)。
错误消失了。
请注意,顺序在这里很重要。当我第一次将
io.netty.resolver.dns.DnsServerAddressStreamProviders$DefaultProviderHolder初始化移动 到运行时但没有接触io.netty.resolver.dns.DefaultDnsServerAddressStreamProvider初始化时,错误报告没有一点变化。所以这需要一点耐心和试验(或一些我没有的知识,唉)。
现在我们有这个:
Error: com.oracle.graal.pointsto.constraints.UnsupportedFeatureException: No instances of java.net.Inet6Address are allowed in the image heap as this class should be initialized at image runtime. Object has been initialized without the native-image initialization instrumentation and the stack trace can't be tracked.
Detailed message:
Trace:
at parsing io.netty.resolver.dns.DefaultDnsServerAddressStreamProvider.<clinit>(DefaultDnsServerAddressStreamProvider.java:87)
Call path from entry point to io.netty.resolver.dns.DefaultDnsServerAddressStreamProvider.<clinit>():
no path found from entry point to target method
Run Code Online (Sandbox Code Playgroud)
好的,它NetUtil.LOCALHOST正在被引用,所以让我们也为它添加一个替换(to NetUtilSubstitutions):
@Alias
@InjectAccessors(NetUtilLocalhostAccessor.class)
public static InetAddress LOCALHOST;
// NOTE: this is the simpliest implementation I could invent to just demonstrate the idea; it is probably not
// too efficient. An efficient implementation would only have getter and it would compute the InetAddress
// there; but the post is already very long, and NetUtil.LOCALHOST computation logic in Netty is rather cumbersome.
private static class NetUtilLocalhostAccessor {
private static volatile InetAddress ADDR;
static InetAddress get() {
return ADDR;
}
static void set(InetAddress addr) {
ADDR = addr;
}
}
Run Code Online (Sandbox Code Playgroud)
这使得最终错误消失。
感谢@NicolasFilotto 对第 5 项的建议,我比原来更喜欢他的解决方案,实际上第 5 项是他想法的实现。
@RecomputeFieldValue,但它受到更多限制,我无法让它在这个与 Netty 相关的任务中工作。附注。替换相关代码的灵感来自https://github.com/quarkusio/quarkus/pull/5353/files
聚苯乙烯。第 5 项解决方案的灵感来自 @NicolasFilotto
| 归档时间: |
|
| 查看次数: |
797 次 |
| 最近记录: |