在 Java 虚拟机中,对象在内存中的结构可以划分为4部分区域:
- markword
- 类型指针(_klass)
- 实例数据(instance_data)
- 对齐填充(padding)
我们用 Java Object Layout 工具来看下,首先创建一个 Maven 工程,并依赖 JOL 二方包:
Maven Jol
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
在 main 方法中编写代码:
Object obj = new Object();
System.out.println(ClassLayout.parseInstance(obj).toPrintable());
运行并查看结果:
java.lang.Object object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x0000000000000001 (non-biasable; age: 0)
8 4 (object header: class) 0xf80001e5
12 4 (object alignment gap)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
从结果我们可以得知 markword 占用对象大小的 8 字节, 类型指针占用 4 字节,由于 java.lang.Object 中没有属性,因此没有这部分内存占用。而 Java 对象为了实现 8 字节对齐,实际大小都是 8 字节的倍数。为了实现对齐填充,最后占用了额外的 4 字节,使得最终大小为 16 字节。
markword
markword 中包含了和 Java 对象息息相关的一些信息,它的实际大小一般和 CPU 字长保持一致,如在 32 位CPU上 markword的大小一般为 32位,即 4 字节;而在 64 位CPU上 markword 的大小一般为 64 位,即 8 字节。由于我的CPU是 64位的,并且 JVM 也是使用的 64 位的,所以这里可以看到 markword 占用了 8 字节大小。当然,如果你在64位CPU上调用了32位的JDK程序,执行效果和32位CPU上的执行效果是一致的。
下载链接
markword 中一般包含三类信息,GC年龄、锁标识和对象 hashCode。
对象在创建后 hashCode 并没有立即更新到对象头当中,当我们调用了 hashCode方法后,hashCode 才会被写入到 hashCode 当中。
Object obj = new Object();
System.out.println(ClassLayout.parseInstance(obj).toPrintable());
obj.hashCode();
// 在调用 hashCode 方法后,可以看到对象头内容发生了变化
System.out.println(ClassLayout.parseInstance(obj).toPrintable());
hashCode 的计算方式在不同的JVM中具体实现逻辑不一样,当两个对象都是使用默认的hashCode 方法时,JVM可以保证这两个对象生成的hashCode是不同的。根据当前对象的地址进行计算的 ,我们可以自定义Java类并重写hashCode
方法。在这种情况下重写的 hashCode 方法并不会同步更新对象头。
markword 中还包含了Java 对象的分代年龄,我们可以通过调用System.gc()
手动触发 GC 来查看分代年龄的变化:
Object obj = new Object();
System.out.println(ClassLayout.parseInstance(obj).toPrintable());
System.gc();
System.out.println(ClassLayout.parseInstance(obj).toPrintable());
除此之外,我们还可以通过使用 synchronized
关键字来查看对象头的变化:
Object obj = new Object();
System.out.println(ClassLayout.parseInstance(obj).toPrintable());
synchronized (obj) {
System.out.println(ClassLayout.parseInstance(obj).toPrintable());
}
我们还可以用这个工具查看一下类的信息:
System.out.println(ClassLayout.parseClass(String.class).toPrintable());
由于类对象是由类加载器加载的C++对象,所以这里无法获取到对应的markword
和 class 指针等信息.
参考资料
JOL (Java Object Layout)
Java Object Layout (JOL)
Java对象结构详解
Java对象在内存的结构
浅谈Java中的hashcode方法