我有一个小Java应用程序,它使用Micronaut 2.0.0实现了一个RESTful API。在引擎盖下,它使用Redisson 3.13.1进入Redis。Redisson则使用Netty(4.1.49)。
该应用程序在“经典”java(在HotSpot上,java 8和java 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一样。网InetAddress中既没有本机方法,也没有子类java。网Inet4Address
可以在构建时初始化。这意味着Inet4Address
的实例对于在构建时初始化的代码(用Java术语来说,在初始化阶段)不可见。原生图像构建器找到了一种方法,可以达到这样一个点:这样的对象是可见的。它甚至显示了跟踪,但问题是,ClusterConnectionManager$1
是一个可运行的
,只在运行时提交给执行者
(静态初始化后的WAAAY)。
如何调试这种情况?即:
另外,如果我添加——在运行时初始化=java。网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报告自己为构建25.252-b09-jvmci-20.1-b02,混合模式
。
PPS。我发现这个在映像堆中不允许...的实例,因为这个类应该在映像运行时初始化,而且Quarkus问题似乎已经解决了。但是我仍然不明白如何解决手头的问题。如果有任何帮助,将不胜感激。
太长,读不下去了答案后面有一小段摘要。
在Java中,每个类在使用前都必须初始化。初始化意味着执行静态字段初始值设定项和静态初始化块。当然,在标准的JVM(比如HotSpot)中,这是在运行时发生的。
但对于原生形象,你有两种选择。类仍然可以在运行时初始化,也可以在构建时进行初始化。后者有一个明显的好处,即在本机映像启动时避免了这项工作,从而使映像启动更快。但对于某些类来说,在构建时初始化它们是没有意义的。例如,一个类可以根据环境(环境变量、配置文件等)在初始化时做出一些决定(例如,创建这个或那个类的实例)。
在构建/运行时初始化备选方案之间进行选择有一些限制:
自19.3.0版以来,nate-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 \
在它的原生图像中。属性
META-INF
,micronaut将netty codec http
作为一个依赖项,因此所有io。netty
类默认在构建时初始化(因为本机映像
工具尊重此类本机映像.属性
文件)。
在这里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);
这个项目还有
——在构建时初始化=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
is
return LOCALHOST_ADDRESS;
并且它引用了名为
命令来避免在构建时初始化它:LOCALHOST_ADDRESS
的静态字段,类型为InetAddress
。让我们通过将以下内容添加到本机映像
--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
is
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的初始化。内蒂。分解器。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的初始化。内蒂。分解器。dns。DnsServerAddressStreamProviders$DefaultProviderHolder
也可以运行:
'--initialize-at-run-time=io.netty.resolver.dns.DnsServerAddressStreamProviders$DefaultProviderHolder'
(注意单引号:如果没有它们,
$
,后面的字符将被sh
解释并替换为空字符串)。
错误消失了。
请注意,订单在这里很重要。当我第一次搬家的时候。内蒂。分解器。dns。DnsServerAddressStreamProviders$DefaultProviderHolder
初始化到运行时,但未触摸到io。内蒂。分解器。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
,但它更受限制,我无法在这个复杂的相关任务中使用它
PS.替代相关代码的灵感来自https://github.com/quarkusio/quarkus/pull/5353/files
PPS。第5项解决方案的灵感来自@NicolasFilotto
我正在使用以下堆栈运行一个简单的Java应用程序(只是一个RESTendpoint,一个“Hello”响应——正是Quarkus maven原型生成的示例,没有修改): Quarkus(微轮廓) JDK 1.8 HotSpot 1.8.0_231-b11 GraalVM 19.3.0(本地图像) MacOS Catalina 第一个错误是(尽管这似乎不是主要问题): 真正的问题(我认为)在这里:
Quarkus文档解释了如何使用Maven构建docker映像。但我在利用Gradle和Kotlin。我正在想办法做这些步骤: 首先,我想我想要一个包含gradle而不是maven的图像(Quay.io似乎没有)。然后,我必须考虑我还需要复制什么(而不是pom.xml),也许???其次,我相信这个图像也包含了GraalVM。所以我不确定我能在那里做些什么。 我必须使用Maven吗?我真的真的不想。
有可能在Java 16下构建本机Quarkus映像吗?没有找到任何操作说明。 不知何故,这应该是可能的,因为Oracle发布了对Java16的GraalVM支持(https://www.graalvm.org/release-notes/21_1/)
在我的项目的根目录中,我发出了以下命令来创建一个特定于我的操作系统的本机可执行文件。有关详细信息,请参阅以下指南 这是输出。 本机映像生成失败,因为构建刚刚挂起。 我选择在主机上安装Graal VM,并选择非基于容器的方法,如下所示。 > ❯ brew安装——cask GRALVM/tap/GRALVM-ce-lts-java11 将Graal VM添加到JEnv管理的Java版本 ❯ jenv
我正在试图弄清楚如何通过将GraalVM本机映像代理与Quarkus应用程序一起使用。 我试着跑步: 但是我得到了一个错误: 有什么提示吗? 我的Java版本:
我在这里将Micronaut应用程序作为Graalvm本地映像进行试用。