JVM 的偏向锁优化在没有竞争时非常高效,但它有一个明显的前提:锁对象从始至终只被一个线程访问。一旦这个前提被打破,偏向锁反而需要付出更高的撤销成本,尤其在高并发共享资源的场景下,频繁撤销带来的安全点停顿会直接影响吞吐量。因此很多性能调优实践会建议在竞争激烈的应用中手动关闭偏向锁,本文就围绕这一参数调整展开分析。

一、偏向锁的设计初衷与撤销开销从何而来
偏向锁的核心思路很简单:如果一个锁对象从创建到回收始终只有一个线程访问,那么同步操作可以完全省略。偏向锁会在对象头的 Mark Word 中记录持有线程的 ID,并将对象标记为偏向状态。线程进入同步块时,只需要检查 Mark Word 中的线程 ID 是否与自己匹配,如果匹配就可以直接进入临界区,不需要执行 CAS 操作,也不需要进入内核态。
这种设计对单线程或极低竞争场景非常有利,能显著降低同步成本。但问题在于,当第二个线程尝试获取一个已经偏向其他线程的锁时,JVM 必须执行偏向撤销。偏向撤销并不是简单的指针修改,它需要进入全局安全点。在安全点期间,JVM 会暂停持有偏向锁的线程,检查该线程是否仍然处在同步块中,然后根据情况将锁恢复为无锁状态或升级为轻量级锁。
如果竞争频繁,这种撤销会被反复触发。每一次撤销都伴随着安全点停顿,即使临界区内的业务代码非常短,撤销开销也可能超过同步本身。因此在高并发下,偏向锁从最初的优化手段变成了性能负担,很多需要精细调优的 Java 服务会选择将它手动关闭。
二、高并发场景下偏向锁的典型性能问题
在实际应用中,连接池、线程池、缓存等共享资源往往会被大量线程同时访问,synchronized 同步块内的锁竞争非常激烈。默认情况下,当第一个线程访问锁对象后会获得偏向,随后的线程再访问时就会触发一次偏向撤销。如果线程切换频繁,JVM 还可能进入批量撤销或批量重偏向流程,这些操作都需要在安全点扫描线程栈,进一步放大停顿时间。
下面这段代码模拟了四个线程同时竞争一把锁的情况。如果在偏向锁开启的状态下运行,JVM 需要在每个线程第一次竞争时执行撤销,并且撤销过程会打断正常执行。虽然单个撤销时间可能只有几毫秒,但当并发量大、锁对象多的时候,累积开销会非常可观。
public class BiasedLockContention {
private static final Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
Thread[] threads = new Thread[4];
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(new Runnable() {
public void run() {
for (int j = 0; j < 200000; j++) {
synchronized (lock) {
// 临界区操作
int sum = j + 1;
}
}
}
});
threads[i].start();
}
for (Thread t : threads) {
t.join();
}
System.out.println("finished");
}
}
在这类场景中禁用偏向锁,可以让锁在第一次发生竞争时直接进入轻量级锁状态。轻量级锁通过 CAS 自旋来竞争,虽然也有一定的 CPU 消耗,但不会频繁触发安全点,也不会产生偏向撤销的额外停顿。从停止世界时间和延迟稳定性角度看,关闭偏向锁往往更有利。
三、手动禁用偏向锁的启动参数与验证
要手动禁用偏向锁,可以在启动 JVM 时添加参数 -XX:-UseBiasedLocking。JVM 的布尔参数使用加号表示开启,减号表示关闭,因此 -XX:-UseBiasedLocking 表示禁用偏向锁。例如在启动一个独立 Java 应用时,可以这样配置:
java -XX:-UseBiasedLocking -Xms512m -Xmx512m -jar app.jar
需要注意的是,偏向锁的默认状态在不同 JDK 版本中并不相同。JDK 8 和 JDK 11 中偏向锁默认开启,因此手动禁用对高并发应用有实际意义。而从 JDK 15 开始,偏向锁默认被关闭,并且相关参数被标记为弃用,在更新的 JDK 版本中这个参数可能不再产生实际效果。配置前应当先确认运行环境的 JDK 版本,避免无效调优。
如果希望验证偏向锁是否真的被关闭,可以使用 JOL 工具打印对象头信息。下面的示例通过 ClassLayout 输出锁对象的 Mark Word 状态,偏向锁关闭后的对象会直接显示为无锁状态。
import org.openjdk.jol.info.ClassLayout;
public class LockHeaderCheck {
public static void main(String[] args) {
Object lock = new Object();
System.out.println(ClassLayout.parseInstance(lock).toPrintable());
}
}
除了查看对象头,也可以通过 jcmd 命令查看虚拟机参数,确认 UseBiasedLocking 的值是否为 false。合理验证参数是否生效,是避免配置错误的重要步骤。
四、禁用偏向锁后的优化思路与注意事项
关闭偏向锁并不意味着所有场景都能获得性能提升。如果应用中的锁竞争非常低,或者大量锁对象只被单一线程使用,偏向锁仍然能够减少同步操作,此时盲目禁用反而会增加轻量级锁的 CAS 开销。因此是否禁用偏向锁,应该基于压测结果来决定,而不是直接套用某个固定配置。
除了调整启动参数,还可以从代码层面降低锁竞争。缩小 synchronized 块的范围、减少锁对象的共享、使用无锁数据结构或者拆分锁粒度,这些优化比单纯调整 JVM 参数更持久有效。JVM 的锁消除和锁粗化也会在运行时对同步代码进行优化,开发时尽量避免在热点路径上使用粗粒度同步。
如果应用已经升级到较新的 JDK 版本,偏向锁默认就是关闭的,此时不需要再手动添加参数。但在使用老版本 JDK 的服务中进行性能调优时,-XX:-UseBiasedLocking 仍然是一个简单有效的启动参数,尤其适合锁竞争激烈的在线服务或中间件系统。