Java对象
Java中一个对象在内存中分为三部分:对象头、实例数据、对齐填充。
对象头
对象头主要分为Mark Word、类型指针(Class Pointer)、数组长度(Array Length)三部分。
Mark Word
Mark Word中包含了对象的hashcode、对象分代年龄、偏向锁标志、锁标志、线程ID、锁记录ID等信息,synchronized锁就是通过Mark Word实现的,所以理解Mark Word对后面的内容极为重要。
hashcode是在调用对象的hashcode()方法之后才有的,如果没调用就是空的
hashcode:存储对象的hashcode。age:对象分代年龄,表示被垃圾回收的次数。biased_block:偏向锁标志,1表示偏向,0表示不偏向。lock:锁标志,用不同的值来表示上图中的几种锁的状态。thread_id:在偏向锁状态下,存储偏向的线程的ID。epoch:偏向锁时间戳(这个暂时没太搞明白,后续明白了再来改正)- 锁记录指针:在轻量级锁状态下,存储栈中锁记录的地址。
- 重量级锁指针:在重量级锁状态下,在操作系统中会生成一个
Monitor对象,这个指针就是指向这个Monitor的地址
类型指针
类型指针用来指向类在内存中的位置,在64位虚拟机中长度是64bits,但是JVM默认开启了指针压缩,长度变成了32bits,指针压缩可以通过-XX:-UseCompressedOops参数来进行设置。
数组长度
只有数组类型才有这部分,32位和64位虚拟机中长度都是32bits。
实例数据
实例数据存储的是对象的属性信息和父类的属性信息。
对齐填充
JVM要求对象的起始地址必须是8的整数倍,所以当对象长度不能满足要求时,就需要额外的字节来进行对齐填充到8的整数倍。
查看对象信息
openjdk提供了一个依赖可以方便查看对象信息:
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.17</version>
</dependency>
public class Test {
public static void main(String[] args) {
Test test = new Test();
System.out.println(ClassLayout.parseInstance(test).toPrintable());
}
}
64位虚拟机关闭指针压缩情况下,输出结果
Tips:这里使用的是当前最新版本
0.17,可以看到输出内容的可读性是比较高的,标识出了mark、class、实例数据、对齐填充(对象类型还会有array length),value部分也会标识出锁的状态以及分代年龄,并且是十六进制格式。测试
0.14版本发现value部分是二进制格式,并且可读性不像图中这么好,但是表示的内容都是一样的,这个根据自己情况选择不同版本就好。另外,虚拟机默认开启了指针压缩,如果设置了关闭或者使用的
32位虚拟机,输出结果可能会有所不同。