背景
对于了解Java内存布局的Coder来说,Java对象内存布局主要由头信息+对象变量(引用或原生类型)+对齐组成,而对于数组会添加一个数组长度字段,主要由头信息+对象变量(引用或原生类型)+对齐组成。而对于对象头信息,包含了众多信息,包括synchronized的锁信息、类指针信息等,下面内容主要针对Java对象头信息,做详细的分析。
对象头信息
针对不同的操作系统,会有不同的对象头信息,下面分页32位操作系统和64位操作系统的不同布局
32位虚拟机
|----------------------------------------------------------------------------------------|--------------------|
| Object Header (64 bits) | State |
|-------------------------------------------------------|--------------------------------|--------------------|
| Mark Word (32 bits) | Klass Word (32 bits) | |
|-------------------------------------------------------|--------------------------------|--------------------|
| identity_hashcode:25 | age:4 | biased_lock:1 | lock:2 | OOP to metadata object | Normal |
|-------------------------------------------------------|--------------------------------|--------------------|
| thread:23 | epoch:2 | age:4 | biased_lock:1 | lock:2 | OOP to metadata object | Biased |
|-------------------------------------------------------|--------------------------------|--------------------|
| ptr_to_lock_record:30 | lock:2 | OOP to metadata object | Lightweight Locked |
|-------------------------------------------------------|--------------------------------|--------------------|
| ptr_to_heavyweight_monitor:30 | lock:2 | OOP to metadata object | Heavyweight Locked |
|-------------------------------------------------------|--------------------------------|--------------------|
| | lock:2 | OOP to metadata object | Marked for GC |
|-------------------------------------------------------|--------------------------------|--------------------|
从表格中可以看出,对于32位虚拟机来说,头信息包含了64位,8字节的信息。其中32位作为Mark Word,32位作为Class Word,Class Word表示当前类的类型信息,比如java.lang.Object、java.lang.Integer等信息的类信息引用地址。而Mark Word的信息有多种的变化,主要依赖于当前状态。
对于Normal状态(即无锁状态)(biased_lock = 0, lock = 01)
- identity_hashcode是一个对象的hash值,这个值被填充的比较晚,当对对象第一次调用System.identityHashCode (obj)时,这个hash值将会被计算,并写入到对象头信息中。在其他状态的情况下,多线程竞争此对象时,identity_hashcode将不再存储在对象头中,而是存储在对象监听器中。
- age字段表示对象在年轻代的年龄,由4位表示,最大可表示15,所以一个对象最多经历15此垃圾回收,若没有被清理,则进入到老年代。
- biased_lock表示偏向锁标记,1表示偏向锁启用,0表示未启用
锁状态
从Jdk1.6开始默认开启偏向锁,可以通过,-XX: -UseBiasedLocking关闭
头信息中lock字段有一小表示
值 | 状态 |
---|---|
00 | 轻量级锁 |
01 | 无锁或偏向锁 |
10 | 重量级锁 |
11 | 标记垃圾回收 |
- Biased状态(biased_lock=1, lock=01),即开启偏向锁
- thread字段,在偏向锁状态下,这个对象主要被某一个特定的线程锁持有,这个字段存储这个特定线程的id
- epoch字段,表示偏向时间戳
- Lightweight Locked状态( lock=00),即轻量级锁状态
- 在这种状态下,假设没有不同线程的竞争这个对象锁,或竞争并不明显。这种情况下,避免使用操作系统级别的锁,而是采用JVM提供的原子操作。
- ptr_to_lock_record 指向线程栈中锁记录的指针,通过CAS方式进行设置,在竞争不明显的情况下,效率高于操作系统级别的锁。
- 这种场景下,采用最小的系统阻塞时间,大约10毫秒左右,保证原子操作不会进入休眠状态,而是等待一个很小的周期,并且一旦资源被释放,原子周期就会结束,并立即获取锁对象。
- Heavyweight Locked状态(lock=10),即重量级锁状态
- ptr_to_heavyweight_monitor,如果对象锁争抢比较明显,将会由轻量级锁退化成重量级锁。prt_to_heavyweight_monitor指向重量级锁Monitor,依赖Mutex操作系统的互斥加锁
- Marked for GC状态(lock=11),即标记待垃圾回收
64位虚拟机
|------------------------------------------------------------------------------------------------------------|--------------------|
| Object Header (128 bits) | State |
|------------------------------------------------------------------------------|-----------------------------|--------------------|
| Mark Word (64 bits) | Klass Word (64 bits) | |
|------------------------------------------------------------------------------|-----------------------------|--------------------|
| unused:25 | identity_hashcode:31 | unused:1 | age:4 | biased_lock:1 | lock:2 | OOP to metadata object | Normal |
|------------------------------------------------------------------------------|-----------------------------|--------------------|
| thread:54 | epoch:2 | unused:1 | age:4 | biased_lock:1 | lock:2 | OOP to metadata object | Biased |
|------------------------------------------------------------------------------|-----------------------------|--------------------|
| ptr_to_lock_record:62 | lock:2 | OOP to metadata object | Lightweight Locked |
|------------------------------------------------------------------------------|-----------------------------|--------------------|
| ptr_to_heavyweight_monitor:62 | lock:2 | OOP to metadata object | Heavyweight Locked |
|------------------------------------------------------------------------------|-----------------------------|--------------------|
| | lock:2 | OOP to metadata object | Marked for GC |
|------------------------------------------------------------------------------|-----------------------------|--------------------|
对于64位虚拟机,不开启头指针压缩的情况下,对象头信息包含128位,即16字节。
64位虚拟机,开启头指针压缩
|--------------------------------------------------------------------------------------------------------------|--------------------|
| Object Header (96 bits) | State |
|--------------------------------------------------------------------------------|-----------------------------|--------------------|
| Mark Word (64 bits) | Klass Word (32 bits) | |
|--------------------------------------------------------------------------------|-----------------------------|--------------------|
| unused:25 | identity_hashcode:31 | cms_free:1 | age:4 | biased_lock:1 | lock:2 | OOP to metadata object | Normal |
|--------------------------------------------------------------------------------|-----------------------------|--------------------|
| thread:54 | epoch:2 | cms_free:1 | age:4 | biased_lock:1 | lock:2 | OOP to metadata object | Biased |
|--------------------------------------------------------------------------------|-----------------------------|--------------------|
| ptr_to_lock_record | lock:2 | OOP to metadata object | Lightweight Locked |
|--------------------------------------------------------------------------------|-----------------------------|--------------------|
| ptr_to_heavyweight_monitor | lock:2 | OOP to metadata object | Heavyweight Locked |
|--------------------------------------------------------------------------------|-----------------------------|--------------------|
| | lock:2 | OOP to metadata object | Marked for GC |
|--------------------------------------------------------------------------------|-----------------------------|--------------------|
对于64位虚拟机,开启头指针压缩的情况下,对象头信息包含96位,即12字节。
实战展示
借助openjdk提供的jol工具,展示对象头信息,相应maven依赖如下
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.10</version>
</dependency>
实例代码如下:
public class JavaObjectHeaderTest {
final Object object = new Object();
public static void main(String[] args) throws InterruptedException {
JavaObjectHeaderTest javaHeader = new JavaObjectHeaderTest();
javaHeader.testObject();
}
public void testObject() throws InterruptedException {
System.identityHashCode(object);
for (int i = 0; i < 1; i++) {
new Thread(() -> {
synchronized (object) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()); System.out.println(ClassLayout.parseInstance(object).toPrintable());
}
}, "thread" + i).start();
}
Thread.sleep(10000);
}
}
更改循环的次数,将会输出不同的头信息
在不使用synchronized的情况下,输入结果:
添加synchronized,循环10次的情况
使用2次循环的情况
- 在不添加synchronized的情况,从第一行数据看出,是无锁操作
- 在使用了synchronized的情况,从第一行数据看出,使用了重量级锁
- 第三张图,从第一行数据看出,展示了使用了偏向锁
总结
- 在32位和64位操作系统中,对象头信息占用的字节数不同
- 32位操作系统,对象头信息占用64位,8字节
- 64位操作系统,在不开启头指针压缩的情况下,占用128位,16字节;开启头指针压缩的情况下,占用96位,12字节
- 头指针信息主要分为Mark Word和Class Word
- 根据状态位的不同,头指针Mark Word的信息不同
- Mark Work最后3位表示状态信息,最后两位表示加锁状态,前一位表示是否开启偏向锁