Java 对象结构之 markword

在 Java 虚拟机中,对象在内存中的结构可以划分为4部分区域:
我们用 Java工具来看下,首先创建一个 Maven 工程,并依赖 JOL 二方包:
Maven Jol
org.openjdk.joljol-core
在 main 方法中编写代码:
Object obj = new Object();System.out.println(ClassLayout.parseInstance(obj).toPrintable());
运行并查看结果:
java.lang.Object object internals:OFFSZTYPE DESCRIPTIONVALUE08(object header: mark)0x0000000000000001 (non-biasable; age: 0)84(object header: class)0xf80001e5124(object alignment gap)Instance size: 16 bytesSpace 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上的执行效果是一致的 。
【Java 对象结构之 markword】下载链接
中一般包含三类信息,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中的方法