我们这里研究的对象模型是线程操作的最基本的对象,
从最基础的代码开始。
在这个实验中,我们探究的主要是 Java 8 的特性,使用了 org.openjdk.jol 包。以无锁状态的对象在内存中的实际存储情况为例作首先的熟悉。
Java 对象在内存的布局分析
接下来根据具体的输出进行分析。
java.lang.Object object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x0000000000000001 (non-biasable; age: 0)
8 4 (object header: class) 0xf80001e5
12 4 (object alignment gap)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
OFF 是偏移量,SZ 是大小,TYPE DESCRIPTION则是定义与描述,VALUE 是具体的数值。
可以看到,一个 Java 8 中的对象,具体的组成有:
- 对象头 Mark Word
- 对象头类型指针 Class Pointer
- 字节对齐填充
整个对象占 16 字节。
对象头 mark,顾名思义,mark 是标记的意思,意味着这 8 个字节(64 比特)的内容是用于作该对象在内存中的状态定义与基本控制的。
对象头类型指针 class,则是具体指向了某个 class 元数据,这是一种引用的设计,具体的实例对象会存储在堆中。
字节对齐填充则是因为 JVM 实现规定的内存对齐策略,大小需要是 8 的倍数。这是一种保障手段。
尽管在输出中是它们是以十进制展示的,但是在 JVM 中,它们毫无疑问是二进制的形式。我们可以用理解 HTTP 协议的格式的方法来理解内存对象布局。其特性是:固定位置、固定长度、每个比特位都有固定含义。JVM 可以轻松地通过这些固定的比特位来⌈解析⌋对象的元数据,就像 Web 服务器解析 HTTP 请求头一样。
十六进制: 0x0000000000000001
二进制: ... 0000 0001
最后 2 位: 01
将对象头 mark word 的 Value 从十进制转到二进制之后,我们可以看到二进制的末尾为 01,对应的内容定义是无锁态。

而在轻量级锁进行绑定后,我们可以看到不一样的结果。
java.lang.Object object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x000000970fdff488 (thin lock: 0x000000970fdff488)
8 4 (object header: class) 0xf80001e5
12 4 (object alignment gap)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
十六进制: 0x000000970fdff488
二进制: ... 1000 1000
最后 2 位: 00
在这里,十进制的末位 8 对应着 1000,末两位是 00,对应 JVM 中的定义,我们可以很快知道这是一个绑定了轻量级锁的对象。通过定义能得知还可知道该指针的值,在 64 位虚拟机中的当前轻量锁情景,也就是前 62 位,通过该指针可以找到其引用的具体内容。
在这里,我们会发现一个有趣的情况。在一般没有锁的情况下,原本对象头 mark word 中存储了哈希值(值得一提的是,只有实例化了的对象才有哈希值,一个类是没有哈希值的)、年代相关的信息,但是上锁后全部变成了指针的内容,原本的信息去了哪里?
事实上,这些信息被拷贝进入了线程维护的栈中,这个栈是私有的,也就是由线程自己管理、维护和操作的。在栈中有一个 Lock Record 模块,在这里存储了原本维护的对象在内存中的一系列状态数据。当锁释放时,JVM 会通过 CAS 操作将 Lock Record 中备份的原始数据恢复到对象头的 Mark Word 中。
实验完整输出
=== 1.没有锁的状态 ===
java.lang.Object object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x0000000000000001 (non-biasable; age: 0)
8 4 (object header: class) 0xf80001e5
12 4 (object alignment gap)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
=== 2. 主线程持有锁(轻量级锁) ===
java.lang.Object object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x000000970fdff488 (thin lock: 0x000000970fdff488)
8 4 (object header: class) 0xf80001e5
12 4 (object alignment gap)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
=== 3. 释放锁后 ===
java.lang.Object object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x0000000000000001 (non-biasable; age: 0)
8 4 (object header: class) 0xf80001e5
12 4 (object alignment gap)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
CAS 自旋锁
CAS(Compare And Swap),是一种在计算机内部实现的原子操作。
自旋,也就是空转。自旋策略实际上是意在避免线程被频繁挂起和唤醒,从而更有效地利用系统的资源,避免不必要的资源调度损失。
也就是说,CAS 自旋锁实际上是为了两个目的:
- 锁的核心目的是”保护数据安全”(正确性)。
- 自旋/挂起策略的选择是”为了性能优化”(效率)。