gpt4 book ai didi

java - 构建原生镜像时如何调试 'No instances of ... are allowed in the image heap'?

转载 作者:行者123 更新时间:2023-12-03 11:17:38 31 4
gpt4 key购买 nike

我有一个使用 Micronaut 2.0.0 实现 RESTful API 的小 Java 应用程序。在幕后,它使用 Redisson 3.13.1 转到 Redis。 Redisson 反过来使用 Netty (4.1.49)。
该应用程序在“经典”java(在 HotSpot 上,Java 8 和 11)中运行良好。
我正在尝试使用 GraalVM 从此应用程序构建 native 镜像。
命令大概是这样的:

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 术语中)是不可见的。并且 native 图像构建器找到了一种方法,可以达到这样一个对象可见的程度。它甚至显示了痕迹,但事实是 ClusterConnectionManager$1Runnable仅提交给 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.
    Java 报告自己为 build 25.252-b09-jvmci-20.1-b02, mixed mode .
    聚苯乙烯。我找到了这个 No instances of ... are allowed in the image heap as this class should be initialized at image runtime似乎 Quarkus 问题已解决。但我仍然不明白如何解决手头的问题。任何帮助,将不胜感激。

    最佳答案

    TLDR; 答案末尾有一小部分带有摘要。
    一点理论
    在 Java 中,每个类都必须在使用前进行初始化。初始化意味着执行静态字段初始值设定项
    和静态初始化块。在标准 JVM(如 HotSpot)中,这当然发生在运行时。
    但是对于 native 镜像,您有两种选择。一个类可能仍然在运行时被初始化,或者它的初始化
    可以在构建时进行。后者有一个明显的好处,可以避免在原生镜像启动时进行这项工作,
    这使图像启动更快。但是对于某些类,在构建时初始化它们是没有意义的。
    这样的例子可以是一个在初始化时做出一些决定的类(创建这个或那个的实例)
    类,例如)基于环境(环境变量、配置文件等)。
    在构建/运行时初始化替代方案之间进行选择有一些限制:

  • 如果一个类在构建时初始化,它的所有父类(super class)都必须在构建时初始化
  • 如果一个类在运行时初始化,它的所有子类都必须在运行时初始化
  • 某些类必须始终在运行时初始化(见下文)
  • 在运行时初始化的类的实例不能存在于图像堆中(这意味着 没有
    构建时初始化的类或其实例可以(直接或间接)引用这样的运行时初始化
    类实例
    )

  • Inet地址问题
    从 19.3.0 版开始, native-image工具要求 java.net.InetAddress类总是在运行时初始化。
    这(通过限制 2)意味着它的子类, java.net.Inet4Addressjava.net.Inet6Address也必须
    在运行时初始化,这反过来(通过限制 4)意味着你不能有任何 InetAddress引用
    通过构建时初始化的类。
    我们在这里遇到的所有构建失败都是由同样的问题引起的:要么 Inet4AddressInet6Address在图像堆中。
    但是为什么在构建时尝试初始化与 Netty 相关的类呢?
    原来 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 的项目
    行为。
    因此,它是使这个问题曝光的原始项目的有用替代品。
    GraalVM 版本
    我在这里使用的是 20.2.0 版(截至撰写本文时最新发布的版本)。
    诊断和修复
    1
    构建失败并出现以下错误:
    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
    错误消失了。
    2
    现在还有一个:
    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
    3
    现在,还有另一个错误:
    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在构建时初始化,但它的字段 LOCALHOST4LOCALHOST6包含 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值(value)观
    不再存储在图像堆中。错误消失了。
    4
    我们现在有另一个:
    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
    }
    }
    }
    错误消失了。
    5
    这次还有一个:
    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 解释并替换为
    空字符串)。
    错误消失了。

    Please note that the order turned out to be important here. When I first movedio.netty.resolver.dns.DnsServerAddressStreamProviders$DefaultProviderHolder initialization to run-time but did nottouch io.netty.resolver.dns.DefaultDnsServerAddressStreamProvider initialization, the error report did not changea bit. So it takes a little patience and experimenting (or some knowledge which I do not have, alas).


    现在我们有这个:
    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;
    }
    }
    这使得最终错误消失。

    Thanks to @NicolasFilotto for suggestions on item 5, I like his solution a lot more than the original, and actuallyitem 5 is an implementation of his ideas.


    技术总结
  • 首先,您可以找到一个类,该类被移至运行时初始化阶段,导致失败消失。
    为此,您可以在提供的堆栈跟踪中跟踪引用。要考虑的最佳候选者是静态字段。
  • 如果第 1 项没有帮助,您可以尝试创建一个替换
  • 理论上也有可能使用更轻的变体:@RecomputeFieldValue ,但更多的是
    受限,我无法让它在这个与 Netty 相关的任务中工作。

  • 附注。替换相关代码的灵感来自 https://github.com/quarkusio/quarkus/pull/5353/files
    聚苯乙烯。第 5 项解决方案的灵感来自 @NicolasFilotto

    关于java - 构建原生镜像时如何调试 'No instances of ... are allowed in the image heap'?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/63328298/

    31 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com