当前位置: 首页 > 知识库问答 >
问题:

你如何调试一个没有实例的。。。在构建本机映像时,是否允许在映像堆中使用?

越运锋
2023-03-14

我有一个小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)。

如何调试这种情况?即:

  1. 你怎么找到罪犯
  2. 当罪魁祸首被发现时,你如何解决它

另外,如果我添加——在运行时初始化=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问题似乎已经解决了。但是我仍然不明白如何解决手头的问题。如果有任何帮助,将不胜感激。

共有1个答案

别烨熠
2023-03-14

太长,读不下去了答案后面有一小段摘要。

在Java中,每个类在使用前都必须初始化。初始化意味着执行静态字段初始值设定项和静态初始化块。当然,在标准的JVM(比如HotSpot)中,这是在运行时发生的。

但对于原生形象,你有两种选择。类仍然可以在运行时初始化,也可以在构建时进行初始化。后者有一个明显的好处,即在本机映像启动时避免了这项工作,从而使映像启动更快。但对于某些类来说,在构建时初始化它们是没有意义的。例如,一个类可以根据环境(环境变量、配置文件等)在初始化时做出一些决定(例如,创建这个或那个类的实例)。

在构建/运行时初始化备选方案之间进行选择有一些限制:

  • 如果一个类在构建时初始化,那么它的所有超类都必须在构建时初始化

自19.3.0版以来,nate-Image工具要求java.net.InetAddress类始终在运行时初始化。这(通过限制2)意味着它的子类,java.net.Inet4Addressjava.net.Inet6Address也必须在运行时初始化,这反过来(通过限制4)意味着您不能有任何InetAddress被构建时初始化的类引用。

我们在这里遇到的所有构建失败都是由相同的问题引起的:映像堆中的Inet4AddressInet6Address

事实证明,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:659is

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:56is

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. LOCALHOST6NetUtil在构建时初始化,但是它的字段LOCALHOST4LOCALHOST6分别包含Inet4AddressInet6Address的实例。这可以通过替换来解决。我们只需将以下类添加到我们的项目中:

@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是他想法的实现。

  1. 首先,您可以找到一个类,它被移动到运行时初始化阶段,导致失败消失。为此,可以在提供的堆栈跟踪中跟踪引用。最好考虑的是静态字段。<李>
  2. 如果第1项没有帮助,您可以尝试创建一个替代项
  3. 理论上也有可能使用更轻的变体:@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本地映像进行试用。