【问题标题】:Who calls describeConstable(), and when?谁调用 describeConstable(),何时调用?
【发布时间】:2020-10-16 21:48:14
【问题描述】:

我有点高兴地偶然发现了Constable and the like in JDK 15。我基本明白了。

在把编译器理论全部玩了一遍,甚至稍微了解了一点之后,我发现我还有一个问题:谁调用了ConstabledescribeConstable()方法,什么时候调用的? Brian's presentation 似乎暗示它在编译时以某种方式被访问。由于对这些事情很天真,我期待它会出现在the usage page 下的jdk.compiler 或其他东西上。相反,the only consumption seems to be in the jdk.incubator.foreign package。 (显然,我知道它可能会被使用页面未公开的某些私人机器使用;因此我的问题。)

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

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

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

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

【问题讨论】:

  • 到目前为止,您所做的编辑也是我苦苦挣扎的部分。我见过的所有样本都使用任意方法进行引导,而不是describeConstable。可能是 Oracle 的某个人会回复我们...

标签: java constants


【解决方案1】:

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

谁调用了 Constable 的 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(它描述了方法的返回类型和方法参数类型,在本例中为run from Runnable

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

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

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


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


请注意,其他人已经在JVM:Scala 的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(){....}

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

【讨论】:

  • @LairdNelson 这正是the talk you’ve linked yourself 中所说的,需要在编译时执行某些方法的能力。从技术上讲,“找出字节码……”的作用与只执行字节码以获得结果之间没有区别。我真正喜欢这个演讲的地方在于它如何将所有内容描述为直截了当(甚至已经完成),但到目前为止我们得到的只是一堆没有实际预期有用功能的接口。但我想,这使它成为一个典型的 Java API。
  • @LairdNelson 编译器并不关心参数的数量。处理它们的问题比编写它们的问题少。但所有这些都必须是“内在的常量表达式”,正如谈话所说的那样。这些表达式可以任意复杂,只要它们再次由 ICE 组成。方式并没有太大的不同,编译时常量今天处理。
  • 这正是我要说的。正如谈话所描述的,常量的组成必须由“可内建的常量表达式”组成。正如谈话中所说,这条路径不需要与describeConstable() 之后返回的相同。这与编译时常量的工作方式没有太大区别。我可以写,final String s="C"+(int)Math.PI/Thread.NORM_PRIORITY+"DE";,它将是一个编译时常量,因为所有输入都是常量,但常量池中的结果值不会反映这种构造,而是简单的四字母字符串。
  • 好的;我在Constable 的javadoc 中没有看到关于谁可以实现它的任何限制,即在其构造函数中连接到数据库并获取用于构造DynamicConstDesc 的值的对象,该值随后由getConstable() 似乎按照接口的约定是完全合法的。我在Constable 文档中看不到对跟踪常量或 ICE 或任何内容的引用。你是说必须有,而它的遗漏是一个严重的文档错误?
  • @LairdNelson 再看一下我之前评论的例子。不是每个String 都是编译时常量,但通过"C"+(int)Math.PI/Thread.NORM_PRIORITY+"DE" 构造的String 是。表达式的类型只是一方面。同样,表达式必须评估为 Constable 类型 并且 是 ICE 以允许在编译时折叠。这并不限制哪些类型可以实现接口,只是特定的编译器功能(甚至还不存在)取决于 ICE 的正式描述。定义一旦成为事物,肯定会出现在 JLS 中。
猜你喜欢
  • 2015-05-14
  • 1970-01-01
  • 2013-03-05
  • 2015-08-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-02-01
相关资源
最近更新 更多