type
status
date
slug
summary
tags
category
icon
password
创建时间
Oct 31, 2024 02:02 PM
对象头(Object Header)

在JVM 中,每个对象的内存布局主要由两部分组成:
- Mark Word:用于存储对象的运行时数据,包括锁状态、哈希码、GC 分代信息等。
- Klass Pointer:指向对象的类型元数据,帮助JVM确定对象的类型信息。
在 Java 中,Mark Word 是对象头的一部分,用于存储关于对象的信息。不同的状态下,Mark Word 的内容会有所不同。以下是一些常见的标识及其含义:
- 无锁状态(Normal Object):
- 在 32 位 JVM 上:25 bits 用于哈希码(HashCode),4 bits 用于分代年龄(Age),2 bits 固定为
01
。 - 在 64 位 JVM 上:31 bits 用于哈希码,4 bits 用于分代年龄,2 bits 固定为
01
。
- 轻量级锁(Lightweight Locking):Mark Word 存储指向栈中锁记录的指针,并且最后两位是
00
。
- 偏向锁(Biased Locking):
- 最后一位是
1
表示启用偏向模式。 - 偏向线程 ID、Epoch 和分代年龄等信息也被编码在其中。
- 重量级锁/膨胀后的锁(Heavyweight Locking/Bloated Lock):Mark Word 存储指向互斥量 (Monitor) 的指针,并且最后两位是
10
。
- GC 标记阶段使用: 当进行垃圾回收时,对象可能会暂时改变其 Mark Word 来表示 GC 状态,这通常与具体的 GC 实现有关,比如 CMS 或 G1 等。
Mark Word 是实现
synchronized
的关键,因为它会根据锁的状态保存不同的信息,具体包括:- 未锁定状态:Mark Word 存储对象的哈希码和 GC 分代信息。
- 偏向锁状态: Mark Word保存获取该锁的线程ID和一些偏向锁标志位。
- 轻量级锁状态: Mark Word 存储的是指向栈中锁记录的指针。
- 重量级锁状态: Mark Word 存储的是指向 Monitor 对象的指针。
锁的升级与优化
为了提高synchronized的性能, JVM从JDK 1.6开始引入了锁的优化机制,包括偏向锁、轻量级锁、重量级锁, 这些状态会根据线程的竞争情况进行动态升级或降级。
偏向锁
在没有锁竞争的情况下,锁总是"偏向"于第一个获得它的线程。偏向锁通过减少不必要的CAS操作来提高性能。
轻量级锁在没有竞争时(就自己这个线程),每次重入仍然需要执行 CAS 操作。Java 6 中引入了偏向锁来做进一步优化:只有第一次使用 CAS 将线程 ID 设置到对象的 Mark Word 头,之后发现这个线程 ID 是自己的就表示没有竞争,不用重新 CAS。以后只要不发生竞争,这个对象就归该线程所有。
- 加锁过程:当线程第一次请求锁时, JVM 会将该线程的 ID 记录在对象头的 Mark Word 中,表示锁偏向于该线程。后续该线程再进入该锁时,无需进行额外的同步操作。
- 撤销偏向锁:
- 其它线程使用对象:如果在偏向锁持有期间,另一个线程请求同一把锁,JVM 会撤销偏向锁,并升级为轻量级锁。
- 调用对象 hashCode:调用了对象的 hashCode,但偏向锁的对象 MarkWord 中存储的是线程 id,如果调用 hashCode 会导致偏向锁被撤销。
- 轻量级锁会在锁记录中记录 hashCode
- 重量级锁会在 Monitor 中记录 hashCode
- 调用
wait/notify
- 批量重偏向:如果对象虽然被多个线程访问,但没有竞争,这时偏向了线程 T1 的对象仍有机会重新偏向 T2,重偏向会重置对象的 Thread ID。当撤销偏向锁阈值超过 20 次后,jvm 会这样觉得,我是不是偏向错了呢,于是会在给这些对象加锁时重新偏向至加锁线程。具体来说,而当一类对象撤销的次数过多,比如有个Yes类的对象作为偏向锁,经常被撤销,次数到了一定阈值就会把当代的偏向锁废弃,把类的epoch加一。所以当类对象和锁对象的epoch值不等的时候,当前线程可以将该锁重偏向至自己,因为前一代偏向锁已经废弃了。
- 线程1:1-50次加锁都是101,即偏向锁状态;这个没什么问题,线程1首次加的锁,并且没有别的线程竞争,所以对象头是偏向锁状态,对应的 Thread Id 为线程1.
- 线程2:1-19 加的锁都是轻量级锁,即前19次进行了偏向锁撤销,第20次执行了重偏向,线程id指向线程2;

- 批量撤销:当撤销偏向锁阈值超过 40 次后,jvm 会这样觉得,自己确实偏向错了,根本就不该偏向。于是整个类的所有对象都会变为不可偏向的,新建的对象也是不可偏向的。
- 线程1:1-50都结婚了(有偏向的线程了)
- 线程2:1-19都离婚了(锁撤销),zf看不行啊,直接结婚证改名吧,20-40都换老公了(Thread Id 直接换了) // 到达BiasedLockingBulkRebiasThreshold次数
- 线程3:20-40又离婚了(锁撤销),滚蛋,都玩完,谁都不能结婚,结婚的也都给我离婚 (设置为不可偏向状态,正在运行的锁对象会被撤销)// 到达
BiasedLockingBulkRevokeThreshold
次数 - 刚出生的妹子,以后直接不让结婚了。(new出来就是轻量级锁)

轻量级锁(Lightweight Locked)
轻量级锁适用于多个线程短时间内争用同一锁的场景。
- 加锁过程:当线程进入同步块时, JVM会在当前线程的栈帧中创建一个锁记录(Lock Record),并将对象头中的Mark Word拷贝到锁记录中。线程尝试使用 CAS 操作将对象头中的Mark Word更新为指向锁记录的指针。如果成功,则表示该线程获取了锁;如果失败,则表示其他线程已经持有该锁,此时锁会升级为重量级锁。
- 解锁过程:线程退出同步块时, JVM会将对象头中的Mark Word恢复为原始值。
重量级锁(Heavyweight Locking)
当锁竞争激烈时, JVM会升级为重量级锁,重量级锁使用操作系统的互斥量(Mutex)机制来实现线程的阻塞与唤醒。
- 加锁过程:如果线程无法通过轻量级锁获取锁, JVM会将该锁升级为重量级锁,并将当前线程阻塞。
- 解锁过程:当线程释放重量级锁时, JVM会唤醒所有阻塞的线程,允许它们再次尝试获取锁。