前言
Java 是门面向对象的开发语言,那么我们自己编写的 Java 类生成的对象是什么样的?它肯定保存在虚拟机的内存中,但它以怎样的结构来保存的呢?带着疑问往下看看。
关于Klass
Java 层的开发可能不太熟悉 Klass,但肯定熟悉 class,我们只要知道 Klass 是 class 在 JVM 中的表示即可,即 Java class 对应 JVM Klass。C++ 中的继承关系如下:
class MetaspaceObj
class Metadata
class Klass
Klass 类用来描述 Java 类信息,包括描述类型自身布局、类名、父类、子类、兄弟类等等。
关于oop
按前面 class 对应的方式,那么对象也应该有 JVM 内部与之相对应的表示吧?没错,就是 oop(ordinary object pointer),普通对象指针。它的定义如下:
typedef class oopDesc* oop;
其中 oopDesc 类是所有 oop 的基类。在 JVM 中,不同的 oop 用于描述特定类型的对象。比如类对象用 instanceOopDesc,数组用 arrayOopDesc。
Klass+oop模型
Java 对象在 JVM 中的结构如下,包括 header 和对象内容。如下图中,左边的是 instanceOopDesc,即一般的类对象,header 包括了标识和元数据,标识用于存储运行时记录信息,包括哈希码、GC锁和线程锁等等。而右边的为 arrayOopDesc,即数组对象,header 多了个 length,用于记录数组长度。
来个demo
public class Test {
private String[] flag = { "a", "b", "c" };
private String name = "test";
public static void main(String[] args) throws Exception {
Test test = new Test();
String _name = "test";
System.out.println(test.flag);
System.out.println(_name);
}
}
查看 main 线程的栈内存,我们主要是要拿到 Test 对象的地址,即0x000000008a105dd0。
接着用 inspector 来查看0x000000008a105dd0地址对应的 oop,看到这个 oop 就是我们的 Test 类生成的对象结构了,包含了 mark 和 metadata。这里可能会有个疑问,就是前面不是说数组还有一个 length 来表示数组长度的吗?但图中的 flag 数组变量并没有看到 length 啊。
其实数组 oop 并不是没有 length,而是 C++ 并没有声明这个变量,而是通过指针来直接将数组长度保存到对应的内存了,所以这里是看不到的。通过下面具体的实现代码就能清楚了解到原因了。是不是我们就没办法看到这个长度值呢?并不是,下面继续看如何来看这个值。
int length() const {
return *(int*)(((intptr_t)this) + length_offset_in_bytes());
}
void set_length(int length) {
*(int*)(((intptr_t)this) + length_offset_in_bytes()) = length;
}
前面我们可以得到 flag 数组 oop 的地址为0x000000008a105de8。64位机器上_mark为8字节,_metadata为4字节,那么将地址加12,得到0x000000008a105df4。然后用 hsdb 命令行的 examine 得到地址的值,得到0x8a105e0800000003,其中00000003便是。
原文作者:超人汪小建