首页 > 解决方案 > 谁调用 describeConstable(),何时调用?

问题描述

Constable我在 JDK 15 中偶然发现了类似的东西。我基本明白。

把所有的编译原理都玩了一遍,稍微了解了一下,我发现我还有一个问题:谁调用了 aConstabledescribeConstable()方法,什么时候调用的? Brian 的介绍似乎暗示它可以在编译时以某种方式访问​​。由于对这些事情很天真,我希望它会出现在使用页面jdk.compiler或其他地方。相反,唯一的消费似乎是在jdk.incubator.foreign包装中。(显然,我知道它可能会被使用页面未公开的某些私人机器使用;因此我的问题。)

我将 a 放置Thread.dumpStack()describeConstable()一个哑类的实现中,该类实现Constable并返回Optional.ofNullable(null)只是为了看看会发生什么……在编译或运行时什么都没有发生。

(我确实知道,在 JDK 12 之前,如果您想编写动态常量,则必须使用 ASM 或 ByteBuddy 或类似的东西。但在我天真的眼中,它看起来像是Constable允许您的用户类“插入”Java编译器并让它你做不断的写作。我也知道里面的类和方法java.lang.constant主要是为编译器编写者准备的,但Constable在我看来有点例外。最后,我显然明白可以调用任何时候都可以使用这种方法,但这显然不是它的目的。)

编辑:(非常)感谢下面一些非常有帮助和耐心的回答和评论,我想我开始明白了(我不是编译器,这点应该很明显)。虽然我知道一旦一个实例X implements Constable存在,那么ContantDesc它返回的它describeConstable()必须由其他常量描述符(本身)制成,虽然我知道可以在编译时调用“常量工厂”(例如ClassDesc#of()等等),显然必须只接受其他常量作为它们可能需要的任何参数,我仍然不清楚在编译过程中如何首先实例化任意 X implements Constable变量......它正在被编译(!)这样describeConstable()可以在编译时调用它。

请记住,这个问题的答案可能是我对一般编译器的基本了解,或者他们在静态分析期间遇到的各种问题。我只看到一个实例方法 ( describeConstable()),它需要在对象 ( X implements Constable) 的实例上调用,并且为了拥有一个对象的实例,必须调用它的构造函数。我不清楚 Java 编译器如何知道如何X implements Constable使用其任意的、可能的多参数构造函数构造 my ,以便它可以调用describeConstable()它。

标签: javaconstants

解决方案


我会说到目前为止我所理解和知道的。这确实是一个有趣的功能。

谁调用了警员的 describeConstable()

javac将要。

什么时候?

当它第一次被调用/需要时。

更详细的解释。你知道 lambda 是如何编译的吗?如果没有,这里有一个非常简短的介绍(稍后会有很大帮助):

Runnable r = () -> {System.out.println("easy, peasy");};
r.run();

如果您查看字节码,将会有一个invokedynamic调用:

invokedynamic #7,  0 // InvokeDynamic #0:run:()Ljava/lang/Runnable;

反过来,这将调用“引导”方法:

BootstrapMethods:
 0: #39 REF_invokeStatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
  • 引导方法的名称是:LambdaMetafactory::metafactory.

  • 作为输入,它需要一个Lookup(由 JVM 提供)

  • 除其他外,javac提供了一个MethodType(它描述了方法的返回类型和方法参数类型,在这种情况下它是runfrom Runnable

  • 它将返回 a CallSite(在这种情况下它实际上是 a ConstantCallSite)。

因此,用相当简单的话(而且很可能有点错误),invokedynamic将调用绑定到 a ConstantCallSite,它在内部将调用委托给您提供Runnablerun方法的实现(在内部它委托给“去糖”私有定义 lambda 的方法)。这种情况只发生一次,在第一次调用时,所有后续调用都不会经历这种痛苦。我在其他答案中以某种方式提供了更多详细信息,例如此处

相同的机制将用于动态常量(但它必须使用ldc而不是invokedynamic)。“机器”已经在 jdk-11 中提供。注意类的名称:ConstantBootstraps,我们知道为什么是“bootstrap”并且我们知道为什么是“Constant”。如果你看一下这些参数,它肯定会开始变得有意义,因为它真的很像invokedynamicfor lambdas。


现在您知道为什么Constable/ConstantDesc需要:以便引导方法调用正确的实现。在上面的例子中,javac“知道”(推断/推断/等等) lambda 实际上是一个Runnable. 在“恒定动态”的情况下,该信息将被类实现的事实所暗示Constable。这将成为如何建立常数的“秘诀”;至少在我的理解中。


请注意,其他人已经在JVMScala 的lazy. 但是他们只是在窗帘后面实施了双重检查锁定volatile,有时您需要为阅读JVM付费…… 到什么程度以及具体如何尚不清楚;因为这还没有实现javac至少在主流 jdk 中是这样。可能是这样的:

// made-up syntax
__@lazy__
private static final MyObject obj = null;

这最终将委托给Constable::describeConstable或可能是:

__@lazy(provider="myProvider")__
private static final MyObject obj = null;

private MyObject myProvider(){....}

但我敢打赌,比我聪明得多的人会想出我在这里没有提到的如何使用它的想法。当这种情况发生时(我知道它会发生),我需要更新这篇文章。


推荐阅读