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