【问题标题】:Java Runtime constant pool reference variable in stack frame堆栈帧中的 Java 运行时常量池引用变量
【发布时间】:2018-05-13 00:07:39
【问题描述】:

根据以下链接,java 堆栈帧包含局部变量、操作数堆栈和当前类常量池引用。 http://blog.jamesdbloom.com/JVMInternals.html

也来自 Oracle“JVM 结构”第 2.6.3 节。 “动态链接 - 每个帧(第 2.6 节)都包含对当前方法类型的运行时常量池(第 2.5.5 节)的引用,以支持方法代码的动态链接。”

我还读到堆中的对象也有一个指向类数据的指针/引用。 https://www.artima.com/insidejvm/ed2/jvm6.html

堆栈帧将包含“当前类常量池引用”,并且它还将具有对堆中对象的引用,该对象又将指向类数据。这不是多余的吗??

例如。

public class Honda {
  public void run() {
    System.out.println("honda is running");
  } 
  public static void main(String[] args) {
  Honda h = new Honda();
  h.run(); //output honda is running
  }
}

当 h.run() 将被执行时,jvm 将创建一个新的堆栈帧并将 h 压入堆栈帧。 h 将指向堆中的对象,该对象又将具有指向 Honda 类数据的指针。堆栈帧也将具有当前类常量引用。它是否正确?如果不是,请对此有所了解。

【问题讨论】:

  • 静态方法在堆栈上没有this 引用。
  • ...您的三个引用中有两个是非规范性的,我的意思是没有地位或地位的任意互联网垃圾。
  • 为了证明@EJP 的非规范点,first link 有一个部分“Operand Stack”表示int i; 将生成 2 字节代码指令:iconst_0 和 @987654329 @。当然不是这样,因为局部变量没有默认值。语句int i = 0; 将生成该字节码,但不会生成文章中的代码。文章有缺陷
  • 事实上,没有初始化器的本地声明根本不会生成任何字节码。 @Andreas。

标签: java jvm


【解决方案1】:

这不是多余的吗??

也许它对于实例方法和构造函数是多余的。

对于静态方法或类初始化伪方法来说不是多余的。


JIT 编译器也可能优化掉(假定的)冗余引用。 (或者它可能没有被优化掉......因为他们已经得出结论,冗余导致执行速度更快平均。)或者可能是 JVM 的实际实现1只是不一样。

请记住,JVM 规范描述的是理想化的堆栈帧。实际的实现可能会有所不同……前提是它表现符合规范规定的方式。


关于@EJP 关于规范性的观点,Java 的唯一规范性参考是 JLS 和 JVM 规范,以及类库的 Javadoc。您还可以查阅 JVM 本身的源代码。规范说明应该发生什么,而代码(在某种意义上)说明应该发生什么。您可能会在已发表的论文或网络文章中找到一篇不规范的文章,而且很可能是不正确的或过时的。


1 - 实际实现可能因版本而异,或因供应商而异。此外,我听说过 JVM 实现,其中字节码重写器在类加载时从标准字节码转换为另一种抽象机器语言。从性能的角度来看,这不是一个好主意……但它肯定符合 JVM 规范的精神。

【讨论】:

  • 谢谢。是的,我确实尝试关注 JLS,但是在很多情况下我无法关注它,不得不寻找其他来源,这对我来说是愚蠢的。
  • @cloud 在这种情况下,您应该坚持使用 Oracle Java 教程和其他 Oracle 或 IBM Java 文档等官方资源。那里有可怕的垃圾。考虑到我提到的两个来源还不错,但你真的不能依赖第三方的东西。
  • @EJP 谢谢你的消息来源。从现在开始将尝试参考这些。
  • 一个后续问题。当引用变量属于父类并且指向子类的对象时会发生什么?栈帧中的常量池引用会指向父类还是子类的常量池?
  • 常量池引用需要指向与声明正在执行的方法的类对应的池。无论是哪个类。
【解决方案2】:

堆栈帧将包含“当前类常量池引用”,并且它还将具有对堆中对象的引用,该对象又将指向类数据。这不是多余的吗??

您错过了该陈述的前提条件,或者您错误地引用了它,或者您在哪里看到它是完全错误的。

“对堆中对象的引用”仅为非静态方法添加,它指的是隐藏的this参数。

正如“Local Variables Array”部分所说:

局部变量数组包含方法执行过程中使用的所有变量,包括对this的引用、所有方法参数和其他本地定义的变量。对于类方法(即静态方法),方法参数从零开始,但是,例如方法,零槽保留给this

因此,对于静态方法,没有冗余。

this 存在时,是否可以消除常量池引用?是的,但是需要有不同的方法来定位常量池引用,需要不同的字节码指令,所以这将是一种不同的冗余。

始终在堆栈帧中的已知位置提供常量池引用,从而简化字节码逻辑。

【讨论】:

  • 是的,我的问题是“this”何时出现。抱歉,如果我不能更好地构建它。所以我收集你所说的是,例如当“this”存在时的实例方法并且如果堆栈帧上没有常量池引用,那么使用“this”jvm可以在堆上定位对象,这将导致类数据但是不能直接到常量池,jvm 将不得不再次定位类数据里面的常量池。那么直接引用常量池会更好吗?
  • @cloud (a) 与静态方法一致,并且 (b) 比省略它更高效(因为 (i) 它节省了运行时测试是否为静态方法,并且 (ii) 它节省了间接)。
  • 谢谢@EJP,这是对我想说的话的一个很好的总结。
  • 知道了。谢谢 Andreas 和 EJP。
  • @EJP 好吧,实际上,即使 this 的存在并不意味着获取常量池的方法(以微不足道的方式),因为 this 的实际类型不一定包含代码的类,我们实际需要它的常量池。搜索类层次结构甚至接口不仅效率低下,还需要在某处记录代码的实际声明类,以便能够进行比较,如果我们需要存储与this 实例不同的那种东西,为什么不首先存储对声明类的常量池的引用……
【解决方案3】:

这里有两点。首先,有static 方法在没有this 引用的情况下被调用。其次,对象实例的实际类不一定是我们实际执行其代码的方法的声明类。常量池引用的目的是实现符号引用的解析和代码引用的常量的加载。在这两种情况下,我们都需要包含当前执行代码的类的常量池,即使该方法可能被this 引用的实际类继承(如果private 方法被另一个继承的方法调用,我们有一个使用 this 实例调用的方法,该实例在形式上甚至不继承该方法。

当前执行的代码甚至可能包含在一个接口中,所以我们从来没有它的实例,但仍然是一个带有常量池的类文件,在执行代码时必须可用。这不仅适用于 Java 8 和更新版本,它们允许在接口中使用 staticdefault 方法;早期版本还可能需要执行接口的<clinit> 方法来初始化其static 字段。

顺便说一句,即使在第一个局部变量中使用与this 关联的对象引用来调用实例方法,也不需要字节码指令将其保留在那里。如果不需要,它可能会被任意值覆盖,将变量槽重用于其他目的。这并不排除后续指令需要常量池,正如前面所说,它不需要属于this 的实际类。

当然,这个池引用无论如何都是一个逻辑结构。实现可以将代码转换为使用共享池,或者在所有引用都已解析时根本不需要池等。内联后,代码甚至可能不再有专用的堆栈帧。

【讨论】:

    猜你喜欢
    • 2016-08-22
    • 2011-06-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-12-09
    • 1970-01-01
    • 2021-04-03
    相关资源
    最近更新 更多