【问题标题】:JVM, the constant pool, the heap and the addressesJVM、常量池、堆和地址
【发布时间】:2014-06-01 00:41:07
【问题描述】:

如果我在 Jasmin 程序集中创建一个新项目然后存储它,我会使用 aload 指令执行此操作,因为它是一个地址:

    new Object
    dup
    invokespecial.....
    astore_3 ; load the object reference into local variable 3

现在,如果我想从常量池中保存一个字符串...我会用 ldc 创建它,然后也用 aload 保存它:

    ldc "Great string"
    astore_3 ; save the reference to the actual string in the constant pool

现在...这些地址的格式和字节数是否相同?由于我使用相同的指令来加载和存储这些项目,JVM 必须能够区分属于常量池的地址和堆中的地址吗?

在检查字节码时,在我的例子中,常量池中的实际地址似乎只是一个 1 字节的索引(我猜对常量池的主要引用也保存在某个地方)......现在我知道了那是对常量池中 som UTF8 数据的引用,但是实际字符串所在的位置还是只是对其他地方的字节数组的引用?检查堆中“新对象”的地址我一直做不到......基本上,我需要弄清楚这两个内存区域如何使用相同形式的指令以及JVM如何管理判断地址是常量池中的偏移量还是堆中的对象?

【问题讨论】:

  • 我相信您的意思是astore,而不是aloadastore 写入对变量的引用,aload 将变量的引用加载到操作数堆栈。
  • 是的,这是真的……我的鲁莽……谢谢! :)

标签: jvm bytecode jasmin


【解决方案1】:

首先,整个字节码格式只是 VM 提供的抽象。它不一定与运行时代码或内存的实际表示有任何相似之处。

其次,常量池是一个使用 16 位索引的最多 65,535 个条目的表。由于索引小索引和类别 1 类型的常量池是一项常见的任务,因此有一个特殊的速记指令 - ldc。

ldc 指令使用单字节索引,因此它仅可用于前 255 个条目。如果你想访问上面的条目,你需要使用两个字节的形式,ldc_w。情况类似于其他速记指令,例如 aload_3 vs aload 3 vs wide aload 3。

再说一遍,这都是抽象的。实际上,VM 会将常量池转换为更友好的内部格式,并且可以将指向其运行时位置的实际指针编译到代码中。但这只是一种可能的实现方式。

【讨论】:

    【解决方案2】:

    JVM解释的字节码不一定与.class文件中写入的字节码相同。许多 JVM 在不同的执行阶段执行所谓的字节码重写

    HotSpot JVM 也是如此。当一个类被初始化时,HotSpot 用 JVM 特定的 fast_aldc 字节码重写引用常量池中字符串条目的 ldc 字节码,该字节码引用 CP 缓存中的对象(即 java.lang.String 实例)。当第一次执行这样的fast_aldc 字节码时,JVM 解析常量池条目,在 Java Heap 中创建一个字符串,并使用对该字符串的引用填充 CP 缓存。在进一步执行相同的字节码时,JVM 会立即从 CP 缓存中获取引用并将其推送到 Java 堆栈。

    在解释ldc 字节码(或其重写形式)之后,栈顶将包含对Java Heap 中对象的有效引用。 new 字节码产生相同类型的引用。所以不需要区分引用类型。

    这就是解释器的工作原理。当然,在一个方法被 JIT 编译后,就不再有字节码、常量池引用等。所有这些都只是抽象。只是一个模型。

    【讨论】:

    • 优秀的答案!谢谢......我相信你说的是真的,只是一点点评论......我检查了 String 类,似乎每个 String 作为类变量持有一个底层字节缓冲区,它本身也必须是一个数组对象并引用堆......对吗?
    • 是的。当我们说“在 Java 堆中创建了一个新字符串”时,我们的意思是在堆中分配了一个新的 char[] 数组以及包装该数组的 java.lang.String 对象。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-06-10
    • 1970-01-01
    • 1970-01-01
    • 2023-03-20
    • 2012-08-09
    • 2019-06-01
    • 1970-01-01
    相关资源
    最近更新 更多