要实时定位线程争抢的锁,需在JMC中启用Flight Recorder并勾选Lock Profiling,切换至Lock Instances视图按Contended Count排序,右键高竞争锁实例查看堆栈以锚定业务代码位置。

怎么用 JMC 实时看到线程在争抢哪个锁
JMC 默认不自动展开锁竞争细节,得手动打开 Lock Instances 和 Contended Locks 视图。光看线程堆栈容易误判——比如多个线程都卡在 synchronized 方法入口,但实际争的是不同对象实例。必须切到「Lock Instances」页,按「Contended Count」倒序,才能定位到真正被高频争抢的 java.lang.Object 或自定义锁对象。
常见错误是只开「Thread Dump」反复刷新,结果看到一堆 BLOCKED on java.lang.Object@xxxxx 却没法关联到具体业务代码。这时候要右键某个高竞争锁实例 → 「Show Stack Trace」→ 往上翻,找到最靠近业务逻辑那一层的 at com.example.service.OrderService.process 才算锚定位置。
- 启动 JMC 时需确保目标 JVM 加了
-XX:+FlightRecorder(Java 8u40+ 默认开启,但老版本或容器环境常被关掉) - JMC 连接后默认只采集基础事件;必须右上角「Start Recording」→ 勾选「Lock Profiling」和「Garbage Collection」,否则
Contended Locks表为空 - 锁竞争数据有延迟:JMC 每 10–20 秒聚合一次,不是实时流;短时尖峰(
为什么 ReentrantLock.lock() 在 JMC 里不显示为 contended lock
JMC 的锁竞争统计依赖 JVM 内部的「biased locking」和「inflated monitor」机制,只捕获基于 ObjectMonitor 的锁(即 synchronized 和部分 ReentrantLock 的公平模式 fallback)。而 ReentrantLock 非公平模式下直接走 CAS + AQS 队列,不触发 JVM 级锁膨胀,所以不会出现在 Contended Locks 列表里。
这时候不能依赖 JMC 锁视图,得转去「Events」页筛选 jdk.JavaMonitorEnter(只覆盖 synchronized)或用 jstack -l <pid> 查 - parking to wait for <0x...>,再结合 AQS 的 AbstractQueuedSynchronizer$Node 堆栈人工比对。
立即学习“Java免费学习笔记(深入)”;
-
ReentrantLock.isFair() == true时,高竞争下会退化成 monitor 模式,此时才可能进 JMC 的 contended 列表 - 想统一监控,建议把关键路径的
ReentrantLock改成synchronized(仅限简单场景),或加lock.getQueueLength()日志埋点 - JDK 17+ 的
jdk.VirtualThreadPinned事件可间接反映锁阻塞,但和传统锁竞争不是一回事
线上环境开 JFR 录制会不会拖慢 JVM
开锁竞争 profiling 不等于全量飞行记录;只要禁用高开销事件(如 jdk.ObjectAllocationInNewTLAB)、限制录制时长(--duration=60s)并用 --settings=profile(而非 default),CPU 开销通常压在 2% 以内。但有个硬伤:JFR 会强制启用偏向锁撤销(BiasedLockingBulkRevokeThreshold),如果应用大量使用短生命周期对象作锁,可能引发批量撤销导致 STW 尖峰。
- 生产环境务必用
jcmd <pid> VM.unlock_commercial_features确认商业特性已解锁(否则 JFR 被禁) - 避免用
--settings=default:它默认开 100+ 事件,其中jdk.GCHeapSummary每秒采样一次,GC 压力大的时候会加剧停顿 - 容器环境注意
-XX:FlightRecorderOptions=repository=/tmp/jfr,否则默认写 /tmp 可能因磁盘满失败,且 JFR 文件不自动清理
从 JMC 导出的 JFR 文件怎么看锁竞争热点
导出的 .jfr 是二进制,不能直接 grep;得用 JMC 自带的离线分析器(File → Import → Open Flight Recording),或者命令行工具 jfr(JDK 11+ 自带):jfr print --events jdk.JavaMonitorEnter,jdk.JavaMonitorWait recording.jfr | grep -A5 'monitorClass.*java.lang.Object'。
重点不是找单次阻塞,而是看「同一 monitor address 出现在多少个不同线程的 BLOCKED 状态里」。JMC 离线视图里,Lock Instances 表头点「Contended Count」排序后,前 3 名如果都是 java.util.concurrent.ConcurrentHashMap$Node,基本能断定是 Map 并发写冲突,而不是业务层锁设计问题。
- 导出文件别用浏览器下载:某些代理会破坏二进制完整性,导致 JMC 提示
Invalid recording file format - 离线分析时关闭「Auto-refresh」,否则大文件(>500MB)加载时 UI 会卡死
- 如果
Contended Count全是 1,说明没真竞争,可能是线程刚进入同步块就被挂起(比如 I/O 等待),这时要看jdk.ThreadSleep或jdk.SocketRead事件
JMC 的锁分析能力边界很清晰:它擅长暴露「谁在等哪个对象」,但不告诉你「为什么这个对象被频繁争抢」。那个「为什么」,还得回到代码里看锁粒度、循环体是否包着同步块、有没有无谓的 wait() —— 这些地方 JMC 给不了答案,只能靠人盯住堆栈里的第 3~5 行。









