Contended Locks表为空是因为JVM未启用锁采样:需确保启动时加-XX:+FlightRecorder、JMC中Start Recording并勾选Lock Profiling,且ReentrantLock非公平模式不触发JVM级锁膨胀,故不会出现在该视图中。

Contended Locks 视图,但前提是 Flight Recorder 必须启用锁采样 —— 否则你看到的全是空表或零星数据。
为什么 Contended Locks 表里没数据?
不是 JMC 不行,是 JVM 没给你录。默认启动的 JFR 不采集锁竞争事件,必须显式开启:Lock Profiling。
- 启动时漏加
-XX:+FlightRecorder:老版本 JDK(如 8u20)或容器镜像常默认关闭,JMC 连不上或连上后无事件 - 连接后没点
Start Recording→ 勾选Lock Profiling:JMC 默认只开基础事件,Contended Locks属于“按需启用”项,不手动勾就等于没开 - 用了
ReentrantLock非公平模式:它走 AQS + CAS,不触发 JVM monitor 膨胀,Contended Locks列表天然不显示 —— 这时候别死磕视图,转去Events页搜jdk.JavaMonitorEnter或用jstack -l <pid></pid>查parking to wait for
怎么从 Contended Count 定位到具体业务代码?
光看「争抢次数高」没用,得关联到实例和调用栈。JMC 不自动帮你跳转,要手动钻取。
- 在
Lock Instances视图中,按Contended Count倒序,找前几条:重点关注java.lang.Object、java.util.HashMap或你自定义的锁对象(如OrderService$LockHolder) - 右键目标锁实例 →
Show Stack Trace:不要只看最顶上几行(比如synchronized方法入口),往上翻,找到第一个at com.yourpackage.开头的类和方法,那才是业务逻辑入口 - 注意锁对象地址(如
@7f8a1c2b)是否重复出现在多个线程堆栈里:如果是,说明是同一个实例被争抢;如果每个线程都卡在不同地址的java.lang.Object@xxx,大概率是对象锁粒度太细或误判
线上开 JFR 锁分析会不会拖慢服务?
会,但可控。关键不在“开不开”,而在“怎么开”。
在调试 Java 后端 API 故障时使用此技能,尤其适用于以下任务:分析日志、跨 Controller、Service 和 Mapper 层追踪请求、定位异常根因、检查 SQL 执行或参数流转,以及生成简洁的诊断结论与修复方案。
- 禁用高开销事件:比如关掉
jdk.ObjectAllocationInNewTLAB(对象分配采样),它对吞吐影响最大 - 用
--settings=profile而非default:前者已做过权衡,锁相关事件保留,其他冗余项精简 - 限制时长:用
duration=30s或jcmd <pid> JFR.start duration=30s</pid>,短时抓取比长时录制更安全 - 避开流量高峰:锁竞争往往在峰值前后 1–2 分钟最明显,不必 24 小时开着 —— 等现象复现再触发一次即可
Contended Locks 数据延迟严重,怎么抓瞬时尖峰?
JMC 的锁聚合是周期性的(通常 10–20 秒),短于这个窗口的竞争事件会被合并或丢弃。
立即学习“Java免费学习笔记(深入)”;
- 单次录制时间不能低于 20 秒,否则可能错过聚合周期 —— 建议设为
duration=60s,确保至少覆盖 3 个聚合窗口 - 如果已知尖峰发生在某秒级区间(比如支付回调集中触发),优先用
jstack -l <pid></pid>抓快照:它不依赖 JFR,毫秒级定格,搜索BLOCKED on或waiting to lock更直接 - 对高频小竞争(如每秒上千次
synchronized块),JMC 可能只记下“总次数”,丢失分布细节 —— 这时候得靠代码埋点,比如在锁入口打日志:log.debug("lock enter, queueLen={}", lock.getQueueLength())
static final Object 被五个服务共用,或者 ConcurrentHashMap 的某个桶被热点 key 锁死。JMC 给的是线索,不是结论 —— Contended Count 高只是起点,真正要盯住的是那个反复出现在堆栈顶部的业务类名。

















