我有一个小的Java应用程序,它使用Micronaut 2.0.0实现RESTful API。在后台,它使用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
这就是我得到的:
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)
这只是输出的一部分,它还会针对其他3个错误生成类似的报告。
我仍在努力理解这个问题,但是我想,由于java.net.InetAddress
中包含本机方法,因此它或其子类java.net.Inet4Address
都无法在构建时初始化。这意味着Inet4Address
的实例对于在构建时初始化的代码(在Java的初始化阶段)不可见。并且本机图像生成器找到了一种方法,可以使这种对象可见。它甚至显示了跟踪,但事实是ClusterConnectionManager$1
是一个Runnable
,仅在运行时(静态初始化之后就被提交给Executor
)。
您如何调试这种情况?即:
PS。如果我添加--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.
Java将自身报告为build 25.252-b09-jvmci-20.1-b02, mixed mode
。
PPS。我找到了这个No instances of ... are allowed in the image heap as this class should be initialized at image runtime,看来Quarkus问题已解决。但是我仍然不知道如何解决当前问题。任何帮助将不胜感激。
答案 0 :(得分:8)
TLDR; ;答案末尾有一小部分带有摘要。
在Java中,必须在使用每个类之前对其进行初始化。初始化意味着执行静态字段初始化程序 和静态初始化块。当然,在标准的JVM(例如HotSpot)中,这是在运行时发生的。
但是对于本机映像,您有两种选择。一个类可能仍在运行时初始化,或其初始化 可以在构建时进行。后者具有避免在本机映像启动时避免这项工作的明显好处, 这样可以使图像启动更快。但是对于某些类,在构建时初始化它们是没有意义的。 这样的示例可能是一个在初始化时做出一些决定的类(创建this或 例如,基于环境(环境变量,配置文件等)的类。
在构建/运行时初始化替代方案之间进行选择存在一些限制:
从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 \
在native-image.properties
下面的META-INF
中,并且micronaut将netty-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();
}
它会产生与以下代码相同的效果(关于Netty):
Config config = new Config();
config.useSingleServer().setAddress(redisUri);
config.useSingleServer().setPassword(redisPassword);
return Redisson.createReactive(config);
此项目的构建脚本中还包含--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)
DnsNameResolver:659
是
return LOCALHOST_ADDRESS;
,它引用类型为LOCALHOST_ADDRESS
的名为InetAddress
的静态字段。让我们避免其初始化
在构建时,将以下内容添加到native-image
命令`中:
--initialize-at-run-time=io.netty.resolver.dns.DnsNameResolver
错误消失了。
现在有另一个:
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)
DnsNameResolverBuilder:56
是
private HostsFileEntriesResolver hostsFileEntriesResolver = HostsFileEntriesResolver.DEFAULT;
让我们推迟HostsFileEntriesResolver
初始化:
--initialize-at-run-time=io.netty.resolver.HostsFileEntriesResolver
现在,还有另一个错误:
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)
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");
}
}
}
}
这个想法是用我们控制的方法调用来替换问题字段的负载。这实现了
通过替换(注意@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)
从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
}
}
}
错误消失了。
这一次还有一个:
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)
首先,让我们将io.netty.resolver.dns.DefaultDnsServerAddressStreamProvider
的初始化移动到
运行时:
--initialize-at-run-time=io.netty.resolver.dns.DefaultDnsServerAddressStreamProvider
现在,错误是相似的,但仍然略有不同:
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)
好吧,让我们将io.netty.resolver.dns.DnsServerAddressStreamProviders$DefaultProviderHolder
的初始化移动到
以及运行时:
'--initialize-at-run-time=io.netty.resolver.dns.DnsServerAddressStreamProviders$DefaultProviderHolder'
(请注意单引号:没有$
及其后的字符将由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
好吧,它是NetUtil.LOCALHOST
的引用,因此我们也要为其添加一个替代项(到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;
}
}
这使最后的错误消失了。
感谢@NicolasFilotto对第5项的建议,我非常喜欢他的解决方案,而实际上 项目5是他的想法的实现。
@RecomputeFieldValue
,但更多
受限制,我无法使其完成与Netty相关的任务。PS。与替换相关的代码受https://github.com/quarkusio/quarkus/pull/5353/files
的启发PPS。第5项解决方案的灵感来自@NicolasFilotto