生成偏向锁 + JIT 优化专题文档
深入 JVM 锁优化:偏向锁撤销与 JIT 锁消除的底层实现
日期 :2026-05-13
标签 :JVM、偏向锁、JIT、锁消除、锁粗化、逃逸分析、C2编译器、HotSpot源码
阅读建议:配合 OpenJDK 8/11/17 源码阅读,理解 JVM 如何在"无竞争"和"低竞争"场景下将锁开销降到零
目录
- 偏向锁的设计哲学
- [Mark Word 的偏向锁布局](#Mark Word 的偏向锁布局)
- [偏向锁的获取:只需替换线程 ID](#偏向锁的获取:只需替换线程 ID)
- 偏向锁撤销:单点撤销与批量撤销
- [批量重偏向:epoch 机制的精妙设计](#批量重偏向:epoch 机制的精妙设计)
- 锁消除:逃逸分析的终极优化
- 锁粗化:合并相邻同步块
- 参数配置与性能测试
- [JDK 15 废弃偏向锁的深层原因](#JDK 15 废弃偏向锁的深层原因)
- 总结:锁优化的完整决策树
一、偏向锁的设计哲学
1.1 问题背景
在无竞争场景下,synchronized 即使走轻量级锁(CAS)也有开销:
轻量级锁开销:
1. 线程栈创建 Lock Record(Displaced Mark Word)
2. CAS 操作替换对象头的 Mark Word
3. 解锁时 CAS 恢复 Mark Word
偏向锁的目标 :如果只有一个线程反复进入同步块,直接把线程 ID 写到对象头里 ,后续进入零 CAS、零阻塞。
1.2 核心思想
对象头 Mark Word(偏向锁模式):
┌─────────────────────────────────────────────────────────────┐
│ [线程ID: 54 bits] | [epoch: 2 bits] | [age: 4 bits] | 1 | 0 1 │
│ │ │ 偏向标记 │
│ │ GC年龄 │
│ 批量重偏向的时间戳 │
│ 持有锁的线程 ID(JVM 内部线程指针) │
└─────────────────────────────────────────────────────────────┘
后续该线程再进入 synchronized:
检查 Mark Word 的线程ID == 当前线程ID?
→ 是:直接执行,无任何原子操作!
→ 否:触发撤销(Revoke)
二、Mark Word 的偏向锁布局
2.1 C++ 源码定义
src/hotspot/share/oops/markWord.hpp
cpp
class markWord {
private:
uintptr_t _value;
public:
// 偏向锁的位模式:最后 3 位是 101
enum { biased_lock_pattern = 5 }; // 二进制 101
// 偏向锁布局(64 位 JVM)
enum {
biased_lock_shift = 3, // 偏向标记位偏移
age_shift = 3, // GC年龄偏移
epoch_shift = 7, // epoch偏移
thread_shift = 9, // 线程ID偏移
thread_bits = 54, // 线程ID占54位
epoch_bits = 2, // epoch占2位
age_bits = 4 // GC年龄占4位
};
// 构造偏向锁的 Mark Word
static markWord encode(JavaThread* thread, uint age, int epoch) {
uintptr_t tmp = (uintptr_t(thread) >> 3) << thread_shift;
tmp |= (age & max_age) << age_shift;
tmp |= (epoch & max_epoch) << epoch_shift;
tmp |= biased_lock_pattern; // 最后3位设为101
return markWord(tmp);
}
// 判断是否偏向锁
bool has_bias_pattern() const {
return (mask_bits(value(), biased_lock_mask_in_place) == biased_lock_pattern);
}
// 获取偏向的线程
JavaThread* biased_locker() const {
assert(has_bias_pattern(), "must be biased");
return (JavaThread*) ((value() & thread_mask_in_place) >> thread_shift << 3);
}
// 获取 epoch
int bias_epoch() const {
assert(has_bias_pattern(), "must be biased");
return (value() & epoch_mask_in_place) >> epoch_shift;
}
};
2.2 偏向锁 vs 无锁 vs 轻量级锁 布局对比
┌────────────────────────────────────────────────────────────────────┐
│ 64 位 Mark Word 布局 │
├────────────────────────────────────────────────────────────────────┤
│ 无锁(001) │
│ ├─ unused:25 | hash:31 | unused:1 | age:4 | 0 | 0 | 1 │
│ │
│ 偏向锁(101) │
│ ├─ thread:54 | epoch:2 | age:4 | 1 | 0 | 1 │
│ │
│ 轻量级锁(00) │
│ ├─ ptr_to_lock_record:62 | 0 | 0 │
│ │
│ 重量级锁(10) │
│ ├─ ptr_to_monitor:62 | 1 | 0 │
│ │
│ GC标记(11) │
│ ├─ unused:62 | 1 | 1 │
└────────────────────────────────────────────────────────────────────┘
三、偏向锁的获取:只需替换线程 ID
3.1 首次进入同步块
src/hotspot/share/runtime/synchronizer.cpp
cpp
// 偏向锁的获取入口
static markWord inflate_bias(oop obj, markWord mark, BasicLock* lock, JavaThread* thread) {
// 1. 检查 Class 是否允许偏向(prototype_header 的偏向标记位)
markWord prototype_header = obj->klass()->prototype_header();
if (!prototype_header.has_bias_pattern()) {
// 该类已禁用偏向锁,走轻量级锁逻辑
return NULL;
}
// 2. 检查 epoch 是否匹配(批量重偏向机制)
int epoch = mark.bias_epoch();
int prototype_epoch = prototype_header.bias_epoch();
if (epoch != prototype_epoch) {
// epoch 不匹配,说明发生了批量重偏向,尝试重新偏向当前线程
markWord new_mark = markWord::encode(thread, mark.age(), prototype_epoch);
// CAS 替换对象头
markWord cmp = obj->cas_set_mark(new_mark, mark);
if (cmp == mark) {
return new_mark; // 重偏向成功!
}
// CAS 失败,有其他线程竞争,需要撤销
}
// 3. 检查是否已偏向当前线程
if (mark.biased_locker() == thread) {
// 已经是当前线程的偏向锁,直接返回(零开销!)
return mark;
}
// 4. 已偏向其他线程,需要撤销
return NULL; // 返回 NULL 表示需要走撤销流程
}
3.2 偏向锁获取的性能优势
场景:单线程反复执行 synchronized(obj)
轻量级锁路径(每次):
1. 创建 Lock Record(栈帧操作)
2. CAS 替换 Mark Word(原子操作,总线锁/缓存锁)
3. 执行同步代码
4. CAS 恢复 Mark Word
→ 每次 2 次 CAS
偏向锁路径(首次之后):
1. 检查 Mark Word 的线程ID == 当前线程?(内存读取,无原子操作)
2. 是 → 直接执行
→ 零 CAS!零内存屏障!纯寄存器比较!
四、偏向锁撤销:单点撤销与批量撤销
4.1 单点撤销(Revoke Individual)
触发条件:另一个线程尝试获取已偏向的锁,且 epoch 匹配。
src/hotspot/share/runtime/biasedLocking.cpp
cpp
// 撤销单个对象的偏向锁
void BiasedLocking::revoke_bias(oop obj, bool allow_rebias, JavaThread* requesting_thread, JavaThread** biased_locker) {
markWord mark = obj->mark();
// 1. 检查是否真的是偏向锁
if (!mark.has_bias_pattern()) {
return; // 已经不是偏向锁了
}
// 2. 获取当前偏向的线程
JavaThread* biased_thread = mark.biased_locker();
if (biased_thread == NULL) {
// 匿名偏向(未偏向任何线程),直接改为无锁
markWord new_mark = markWord::prototype_for_klass(obj->klass());
obj->set_mark(new_mark);
return;
}
// 3. 构造新的无锁 Mark Word(保留 hashCode 和 age)
markWord new_mark = obj->klass()->prototype_header();
// 如果对象已经计算过 hashCode,需要保留
if (mark.hash() != 0) {
new_mark = new_mark.copy_set_hash(mark.hash());
}
// 4. 保存原始 Mark Word 到 Lock Record(为了轻量级锁过渡)
markWord saved_mark = mark;
// 5. 替换对象头为无锁状态(001)
obj->set_mark(new_mark.with_bias_pattern_cleared());
// 6. 如果允许重偏向(批量重偏向场景),更新 epoch
if (allow_rebias) {
// 在 safepoint 中由 VMThread 统一处理
}
if (biased_locker != NULL) {
*biased_locker = biased_thread;
}
}
4.2 批量撤销(Bulk Revoke)
问题:如果某个类的对象频繁被不同线程竞争,每次单个撤销成本太高。
触发条件:
- 某个类的撤销计数达到阈值(默认 40 次)
- 或者该类对象的偏向锁失效比例过高
src/hotspot/share/runtime/biasedLocking.cpp
cpp
// 批量撤销:禁用整个类的偏向锁
void BiasedLocking::bulk_revoke_at_safepoint(oop obj, JavaThread* requesting_thread, JavaThread** biased_locker) {
assert(SafepointSynchronize::is_at_safepoint(), "must be at safepoint");
// 1. 撤销当前对象
revoke_bias(obj, false, requesting_thread, biased_locker);
// 2. 获取对象的 Class
Klass* k = obj->klass();
// 3. 【关键】修改 Class 的原型头,永久禁用该类的新对象偏向
markWord new_prototype = k->prototype_header();
new_prototype = new_prototype.with_bias_pattern_cleared(); // 清除 101 标记
k->set_prototype_header(new_prototype);
// 4. 遍历堆中所有该类的对象,撤销偏向
// 注意:只在 GC 的 safepoint 中执行,因为需要遍历堆
revoke_all_instances(k);
// 5. 记录该类已禁用偏向,后续 new 的对象直接是无锁状态
log_info(biasedlocking)("Bulk revoked bias of %s", k->external_name());
}
// 遍历堆中所有实例(在 GC safepoint 中调用)
void BiasedLocking::revoke_all_instances(Klass* k) {
// 通过 GC 的 ObjectClosure 遍历堆
RevokeAllInstancesClosure blk(k);
Universe::heap()->object_iterate(&blk);
}
4.3 撤销的代价
单点撤销开销:
1. 停止当前线程(或进入 safepoint)
2. 替换对象头 Mark Word(CAS)
3. 后续线程走轻量级锁路径(CAS 竞争)
批量撤销开销:
1. 进入全局 Safepoint(所有 Java 线程暂停!)
2. 遍历堆中所有该类对象(可能成千上万)
3. 逐个 CAS 替换 Mark Word
4. 修改 Class 元数据(永久禁用偏向)
→ 批量撤销是"重型操作",会 STW(Stop The World)!
五、批量重偏向:epoch 机制的精妙设计
5.1 问题场景
线程 A 执行完 10000 个对象的 synchronized
→ 这 10000 个对象都偏向 A
线程 B 开始执行这 10000 个对象的 synchronized
→ 如果逐个撤销:10000 次单点撤销 = 性能灾难!
优化思路:能不能一次性把 10000 个对象"转给"B?
5.2 epoch 机制
核心设计:
- 每个 Class 有一个 prototype_epoch(类级别)
- 每个对象的 Mark Word 有一个 bias_epoch(对象级别)
- 当 prototype_epoch != bias_epoch 时,说明发生了批量重偏向,对象可以 CAS 重新偏向新线程
src/hotspot/share/runtime/biasedLocking.cpp
cpp
// 批量重偏向:把一类对象从偏向 A 改为偏向 B
void BiasedLocking::bulk_rebias_at_safepoint(oop obj, JavaThread* requesting_thread) {
assert(SafepointSynchronize::is_at_safepoint(), "must be at safepoint");
Klass* k = obj->klass();
// 1. 获取当前 epoch
markWord prototype_header = k->prototype_header();
int current_epoch = prototype_header.bias_epoch();
int new_epoch = (current_epoch + 1) & markWord::epoch_mask_in_place;
// 2. 更新 Class 的 prototype_header,epoch + 1
markWord new_prototype = markWord::encode(requesting_thread, 0, new_epoch);
k->set_prototype_header(new_prototype);
// 3. 【关键】不遍历对象!epoch 不匹配的对象下次访问时自动重偏向
// 只有 epoch 匹配的对象(还在被原线程使用)需要处理
// 4. 遍历堆,处理 epoch 仍匹配的旧对象
RebiasAllInstancesClosure blk(k, new_epoch, requesting_thread);
Universe::heap()->object_iterate(&blk);
}
// 对象访问时的 epoch 检查(快速路径,无需 safepoint)
markWord markWord::decode_bias(JavaThread* thread, markWord mark, Klass* k) {
int obj_epoch = mark.bias_epoch();
int class_epoch = k->prototype_header().bias_epoch();
if (obj_epoch != class_epoch) {
// 【关键】epoch 不匹配,说明发生了批量重偏向!
// 尝试 CAS 重新偏向当前线程
markWord new_mark = markWord::encode(thread, mark.age(), class_epoch);
// 如果对象已偏向其他线程但 epoch 过期,直接抢过来
return obj->cas_set_mark(new_mark, mark) == mark ? new_mark : NULL;
}
// epoch 匹配,检查线程 ID
if (mark.biased_locker() == thread) {
return mark; // 已偏向当前线程
}
return NULL; // 已偏向其他线程,需要撤销
}
5.3 epoch 机制的优势
批量重偏向前(10000 个对象偏向 A,现在要偏向 B):
旧方案(逐个撤销):
遍历 10000 个对象 → 逐个 CAS 撤销 → 10000 次原子操作
→ 耗时:O(n),n 是对象数量
epoch 方案:
1. 进入 safepoint
2. Class 的 epoch + 1(1 次操作)
3. 遍历对象:只处理 epoch 匹配的(很少)
4. 其他对象下次被访问时,发现自己的 epoch 过期
→ 自动 CAS 重偏向新线程(无需 safepoint!)
→ 耗时:O(活跃对象数),非活跃对象零开销延迟处理
六、锁消除:逃逸分析的终极优化
6.1 什么是逃逸分析?
定义:分析对象的作用域,判断其引用是否"逃逸"出:
- 方法逃逸:作为返回值或被参数传递出去
- 线程逃逸:被其他线程访问
- 全局逃逸:存储到静态变量
src/hotspot/share/opto/escape.cpp(C2 编译器的逃逸分析)
cpp
// 连接图(Connection Graph)节点类型
class PointsToNode : public ResourceObj {
public:
enum EscapeState {
NoEscape = 0, // 不逃逸 → 可标量替换 + 锁消除
ArgEscape = 1, // 逃逸到参数 → 部分优化
GlobalEscape = 2 // 全局逃逸 → 无法优化
};
private:
Node* _node; // IR 节点
EscapeState _escape; // 逃逸状态
public:
bool is_scalar_replaceable() const { return _escape == NoEscape; }
};
// 主分析流程
void ConnectionGraph::compute_escape() {
// 1. 构建连接图:从 new 节点开始,追踪引用流向
build_connection_graph();
// 2. 迭代分析,直到没有变化
bool changed;
do {
changed = false;
// 遍历所有节点,根据使用方式更新逃逸状态
for (uint i = 0; i < _nodes.size(); i++) {
PointsToNode* ptn = _nodes.at(i);
if (ptn->escape_state() == PointsToNode::NoEscape) {
// 检查是否有逃逸的使用
if (has_escape_use(ptn)) {
ptn->set_escape_state(PointsToNode::ArgEscape);
changed = true;
}
}
}
} while (changed);
// 3. 对 NoEscape 的对象进行优化
for (uint i = 0; i < _nodes.size(); i++) {
PointsToNode* ptn = _nodes.at(i);
if (ptn->is_scalar_replaceable()) {
// 标量替换:对象拆成字段,栈上分配
scalar_replace(ptn);
// 锁消除:删除 synchronized
eliminate_locks(ptn);
}
}
}
6.2 锁消除的具体实现
src/hotspot/share/opto/macro.cpp
cpp
// 尝试消除锁节点
bool PhaseMacroExpand::eliminate_locking_node(AbstractLockNode* alnode) {
// 1. 检查锁对象是否逃逸
if (!alnode->obj_node()->is_scalar_replaceable()) {
return false; // 对象逃逸,不能消除
}
// 2. 检查锁范围内是否有可能被其他线程访问的操作
// 例如:调用外部方法、访问 volatile 变量等
if (has_interfering_access(alnode)) {
return false;
}
// 3. 【消除锁】替换 LockNode 为无操作
// 删除 monitorenter 对应的 IR 节点
_igvn.replace_node(alnode, alnode->in(TypeFunc::Control));
// 4. 删除对应的 UnlockNode
AbstractLockNode* unlock = alnode->unlock_node();
if (unlock != NULL) {
_igvn.replace_node(unlock, unlock->in(TypeFunc::Control));
}
// 5. 删除 Lock Record 分配
// 标量替换后,对象本身都不存在了,Lock Record 自然也不需要
if (TraceEliminateLocks) {
tty->print_cr("Eliminated lock node %d for object %d",
alnode->_idx, alnode->obj_node()->_idx);
}
return true;
}
6.3 锁消除的实际案例
Java 代码:
java
public String concat(String s1, String s2) {
// StringBuffer 是局部变量,不逃逸
StringBuffer sb = new StringBuffer();
// StringBuffer.append 内部有 synchronized(this)
// 但 sb 不逃逸,锁可以被消除!
sb.append(s1);
sb.append(s2);
return sb.toString(); // 返回的是新 String,sb 本身没逃逸
}
C2 编译后的伪代码(锁消除后):
java
public String concat(String s1, String s2) {
// 标量替换 + 锁消除后:
// StringBuffer 对象被拆成字段(char[] value, int count)
// synchronized 完全删除
char[] value = new char[s1.length() + s2.length()];
int count = 0;
// 直接操作数组,无锁!
System.arraycopy(s1.toCharArray(), 0, value, 0, s1.length());
count += s1.length();
System.arraycopy(s2.toCharArray(), 0, value, count, s2.length());
count += s2.length();
return new String(value, 0, count);
}
性能对比:
未优化(解释器 / C1):
StringBuffer.append → monitorenter → CAS 竞争 → 业务逻辑 → monitorexit
→ 每次 append 2 次原子操作
C2 优化后:
直接数组拷贝,零同步开销!
→ 性能提升 10-100 倍(取决于竞争程度)
七、锁粗化:合并相邻同步块
7.1 优化场景
java
// 优化前:频繁加锁解锁
for (int i = 0; i < 100; i++) {
synchronized (lock) {
list.add(i); // 每次循环都 enter/exit
}
}
// 锁粗化后:合并为一个同步块
synchronized (lock) {
for (int i = 0; i < 100; i++) {
list.add(i); // 只 enter/exit 一次
}
}
7.2 C2 编译器的锁粗化实现
src/hotspot/share/opto/loopnode.cpp
cpp
// 检查循环内的锁是否可以粗化到循环外
bool PhaseIdealLoop::coarsen_locks_in_loop(IdealLoopTree* loop) {
// 1. 收集循环内所有 LockNode
Unique_Node_List lock_nodes;
for (uint i = 0; i < loop->_body.size(); i++) {
Node* n = loop->_body.at(i);
if (n->is_Lock()) {
lock_nodes.push(n);
}
}
// 2. 检查所有锁是否是同一个对象
Node* lock_obj = NULL;
for (uint i = 0; i < lock_nodes.size(); i++) {
LockNode* lock = lock_nodes.at(i)->as_Lock();
if (lock_obj == NULL) {
lock_obj = lock->obj_node();
} else if (lock_obj != lock->obj_node()) {
return false; // 不同对象,不能粗化
}
}
// 3. 检查循环内是否有:
// - 方法调用(可能逃逸)
// - 异常抛出点(粗化后异常范围变大)
// - 其他线程可见的内存操作
if (has_escaping_call(loop) || has_exception_thrown(loop)) {
return false;
}
// 4. 执行粗化:
// - 在循环前插入 LockNode
// - 在循环后插入 UnlockNode
// - 删除循环内所有原 Lock/Unlock 节点
insert_coarsened_lock(loop, lock_obj);
remove_inner_locks(loop);
return true;
}
7.3 锁粗化的限制
不能粗化的情况:
1. 锁对象不同:synchronized(a) { ... } synchronized(b) { ... }
2. 中间有方法调用:可能逃逸或长时间持有锁
3. 中间有异常抛出:粗化后异常范围变大,语义改变
4. 中间有 I/O 操作:长时间持有锁影响并发
5. 中间有 wait()/notify():语义复杂,不能简单合并
八、参数配置与性能测试
8.1 关键 JVM 参数
| 参数 | 默认值 | 说明 |
|---|---|---|
-XX:+UseBiasedLocking |
JDK8: 开启 JDK15+: 移除 | 启用偏向锁 |
-XX:BiasedLockingStartupDelay=0 |
4000 (ms) | JVM启动后延迟启用偏向锁 |
-XX:+PrintBiasedLockingStatistics |
关闭 | 打印偏向锁统计 |
-XX:+TraceBiasedLocking |
关闭 | 追踪偏向锁事件 |
-XX:+DoEscapeAnalysis |
开启 | 启用逃逸分析(锁消除依赖) |
-XX:+EliminateLocks |
开启 | 启用锁消除 |
-XX:+CoarsenLocks |
开启 | 启用锁粗化 |
-XX:+PrintEscapeAnalysis |
关闭 | 打印逃逸分析结果 |
-XX:+PrintEliminateLocks |
关闭 | 打印锁消除信息 |
8.2 测试代码:验证锁消除
java
public class LockEliminationTest {
// 会被锁消除的方法
public int eliminateLock() {
Object lock = new Object(); // 局部变量,不逃逸
synchronized (lock) { // C2 编译后删除
return 42;
}
}
// 不会被锁消除的方法(对象逃逸)
private Object globalLock = new Object();
public int noEliminate() {
synchronized (globalLock) { // 全局对象,不能消除
return 42;
}
}
public static void main(String[] args) {
LockEliminationTest test = new LockEliminationTest();
// 预热,触发 C2 编译
for (int i = 0; i < 100000; i++) {
test.eliminateLock();
test.noEliminate();
}
// 正式测试
long start = System.nanoTime();
for (int i = 0; i < 10000000; i++) {
test.eliminateLock();
}
long eliminateTime = System.nanoTime() - start;
start = System.nanoTime();
for (int i = 0; i < 10000000; i++) {
test.noEliminate();
}
long noEliminateTime = System.nanoTime() - start;
System.out.println("锁消除: " + eliminateTime / 1_000_000 + " ms");
System.out.println("未消除: " + noEliminateTime / 1_000_000 + " ms");
}
}
运行参数:
bash
java -server -XX:+DoEscapeAnalysis -XX:+EliminateLocks \
-XX:+PrintEscapeAnalysis -XX:+PrintEliminateLocks \
LockEliminationTest
预期输出:
Lock Eliminated: 123 // 锁节点 123 被消除
锁消除: 15 ms // 纯寄存器操作
未消除: 250 ms // 有 CAS 竞争
九、JDK 15 废弃偏向锁的深层原因
9.1 官方 JEP 374 说明
JEP 374: Deprecate and Disable Biased Locking
"Biased locking has a significant amount of complex code in the JVM,
adds complexity to the HotSpot synchronization subsystem, and is
increasingly difficult to maintain."
9.2 废弃的核心原因
| 原因 | 详细说明 |
|---|---|
| 维护成本高 | 偏向锁代码遍布解释器、C1、C2、GC、线程状态管理,耦合严重 |
| 收益降低 | 现代应用多线程竞争激烈,偏向锁触发撤销的频率远高于收益 |
| 撤销代价大 | 批量撤销需要全局 Safepoint,STW 时间不可控 |
| 替代方案成熟 | java.util.concurrent(AQS)+ VarHandle 性能已足够好 |
| 测试覆盖难 | 偏向锁的边界情况(epoch 溢出、线程死亡等)难以全面测试 |
9.3 废弃后的替代方案
java
// 旧代码(依赖偏向锁优化单线程性能)
synchronized (obj) { ... }
// 新方案1:直接使用 j.u.c(无锁化)
Lock lock = new ReentrantLock();
lock.lock();
try { ... } finally { lock.unlock(); }
// 新方案2:原子类(完全无锁)
AtomicInteger counter = new AtomicInteger();
counter.incrementAndGet(); // CAS,无需阻塞
// 新方案3:ThreadLocal(避免共享)
ThreadLocal<SimpleDateFormat> tl = ThreadLocal.withInitial(SimpleDateFormat::new);
// 每个线程一份,无需同步
9.4 各版本默认行为
| JDK 版本 | 偏向锁状态 | 备注 |
|---|---|---|
| 8 | 默认开启 | 延迟 4 秒启用 |
| 11 | 默认开启 | 同上 |
| 15 | 默认关闭 | -XX:+UseBiasedLocking 可手动开启 |
| 18+ | 完全移除 | 参数无效,代码删除 |
十、总结:锁优化的完整决策树
synchronized(obj)
│
▼
┌─────────────────────┐
│ C2 编译器触发了吗? │
└─────────────────────┘
│ │
▼ 是 ▼ 否(解释器/C1)
┌──────────────┐ ┌──────────────┐
│ 逃逸分析 │ │ 检查 Mark Word│
│ obj 逃逸吗? │ │ 锁状态标志 │
└──────────────┘ └──────────────┘
│ │ │ │ │ │
▼ 否 ▼ 是 ▼ ▼ ▼ ▼
┌────────┐ ┌──────┐ 001 101 00 10
│锁消除 │ │保留锁 │ │ │ │ │
│删除同步 │ │ │ ▼ ▼ ▼ ▼
└────────┘ └──────┘ 无锁 偏向 轻量 重量
│ │ │ │
▼ ▼ ▼ ▼
CAS 检查 CAS OS
加锁 线程ID 竞争 阻塞
成功? 匹配?
│ │
▼ 是 ▼ 否
直接执行 撤销
│
▼
轻量级锁
│
▼
CAS 竞争
成功?
│
┌────────────┴────────────┐
▼ 是 ▼ 否
获得锁 自旋/膨胀
│
▼
ObjectMonitor
_EntryList 阻塞
参考源码路径(OpenJDK)
src/hotspot/share/oops/markWord.hpp # Mark Word 位域定义
src/hotspot/share/runtime/biasedLocking.hpp # 偏向锁入口
src/hotspot/share/runtime/biasedLocking.cpp # 撤销/重偏向实现
src/hotspot/share/runtime/synchronizer.cpp # 锁膨胀、进入/退出
src/hotspot/share/runtime/objectMonitor.cpp # Monitor 阻塞/唤醒
src/hotspot/share/opto/escape.cpp # C2 逃逸分析
src/hotspot/share/opto/macro.cpp # 锁消除实现
src/hotspot/share/opto/loopnode.cpp # 锁粗化实现
src/hotspot/share/opto/library_call.cpp # 内建方法优化