生成偏向锁 + JIT

生成偏向锁 + JIT 优化专题文档

深入 JVM 锁优化:偏向锁撤销与 JIT 锁消除的底层实现

日期 :2026-05-13
标签 :JVM、偏向锁、JIT、锁消除、锁粗化、逃逸分析、C2编译器、HotSpot源码
阅读建议:配合 OpenJDK 8/11/17 源码阅读,理解 JVM 如何在"无竞争"和"低竞争"场景下将锁开销降到零


目录

  1. 偏向锁的设计哲学
  2. [Mark Word 的偏向锁布局](#Mark Word 的偏向锁布局)
  3. [偏向锁的获取:只需替换线程 ID](#偏向锁的获取:只需替换线程 ID)
  4. 偏向锁撤销:单点撤销与批量撤销
  5. [批量重偏向:epoch 机制的精妙设计](#批量重偏向:epoch 机制的精妙设计)
  6. 锁消除:逃逸分析的终极优化
  7. 锁粗化:合并相邻同步块
  8. 参数配置与性能测试
  9. [JDK 15 废弃偏向锁的深层原因](#JDK 15 废弃偏向锁的深层原因)
  10. 总结:锁优化的完整决策树

一、偏向锁的设计哲学

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         # 内建方法优化
相关推荐
czt_java1 小时前
线程安全问题
java·开发语言·jvm
likerhood1 小时前
设计模式-装饰器模式(java)
java·设计模式·装饰器模式
爱学习的小可爱卢1 小时前
Java抽象类与接口:面试高频考点全解析
java·javase
WL_Aurora2 小时前
Java多线程详解(一)
java·开发语言
会编程的土豆2 小时前
Go 语言中的 `new` 关键字(创建指针)
java·算法·golang
逸Y 仙X2 小时前
文章三十一:ElasticSearch 管道聚合
java·大数据·elasticsearch·搜索引擎·全文检索
Full Stack Developme2 小时前
Spring 发展历史
java·后端·spring
组合缺一2 小时前
Java 流程编排新范式 Solon Flow:一个引擎,七种节点,覆盖规则/任务/工作流/AI 编排全场景
java·spring·ai·solon·workflow·flow
largecode2 小时前
企业号码认证可以线上办理吗?支持线上申请,设置来电显示品牌名
java·python·智能手机·微信公众平台·facebook·paddle·新浪微博