目录
对象在内存中是如何布局的
在聊到对象加锁这个话题,那就必须先聊聊对象在内存中的布局, 你知道一个对象在内存中是如何布局的吗?一个对象new出来以后,它在内存中主要分为一下四个部分:
markword | 这部分就是加锁的核心,占8个字节 |
klass pointer | 记录指向对象class文件的指针,占4个字节 |
instance data | 对象变量数据 |
padding | 对其数据,在64位版本虚拟机规范中要求对象大小必须是8的倍数,不足部分使用padding补齐 |
final Object monitor = new Object(); 这个monitor 在内存中的大小是多少字节呢?答案是16个字节,8+4+0=12,12不能不8整除,所以补齐后的大小为16;
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x0000000000000005 (biasable; age: 0)
8 4 (object header: class) 0xf80001e5
12 4 (object alignment gap)
如何查看对象在内存中的布局
我们可以通过JOL(Java Object Layout)来查看对象在内存中的布局;
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.16</version>
</dependency>
public static void main(String[] args) {
//JKD8延迟4S开启偏向锁
Thread.sleep(5000);
final Object monitor = new Object();
System.out.println(ClassLayout.parseInstance(monitor).toPrintable());
}
这个代码的运行结果就是上文中的布局信息。
markword数据结构
在上文中提到markword是对象加锁的核心,那么这部分数据的结果是什么样子呢?下面就来介绍一下markword数据结构;这也一些大厂面试中经常会遇到的部分。
锁状态 | 56 bit | 1bit | 4bit | 1bit | 2bit | ||
---|---|---|---|---|---|---|---|
是否偏向锁 | 锁状态 | ||||||
无锁 | unused 25bit | hashcode 31bit | unused | 分代年龄 | 0 | 01 | |
偏向锁 | 线程ID | epoch 2bit | unused | 分代年龄 | 1 | 01 | |
轻量级锁 | 指向战中锁记录的指针 | 00 | |||||
重量级锁 | 指向互斥锁的指针 | 10 | |||||
GC | 11 |
加锁后发生了什么
聊完了JOL以后,我们来看看对象加锁后到底发生了什么?我们通过如下一段代码来看看:
public static void main(String[] args) {
final Object obj = new Object();
System.out.println("启动后对象布局:\n" + ClassLayout.parseInstance(obj).toPrintable());
//JKD8延迟4S开启偏向锁
Thread.sleep(5000);
//可偏向 101
final Object monitor = new Object();
System.out.println("延迟5秒后对象布局:\n" + ClassLayout.parseInstance(monitor).toPrintable());
//偏向锁
synchronized (monitor) {
System.out.println("对象加锁后的布局:\n" + ClassLayout.parseInstance(monitor).toPrintable());
}
System.out.println("对象释放锁后的布局:\n" + ClassLayout.parseInstance(monitor).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: cl