如何获取运行中JVM的闲置监视器数量?排查GC停顿问题
JVM监视器数量获取与GC Safepoint Sync阶段耗时排查
一、JDK-8153224中获取当前监视器数量的方式
该案例里是通过HotSpot的诊断工具和内部接口获取的,具体两种常用方式:
- 用
jcmd <进程ID> GC.class_histogram命令,统计所有java.lang.Object的实例总数——因为每个Java对象都关联着一个监视器结构(即使没被加锁),这个数值可以近似当前JVM的总监视器数量。 - 借助HotSpot的JVMTI(Java虚拟机工具接口)编写自定义工具,遍历JVM内所有对象,直接统计关联的监视器结构数量,这种方式更精准。
二、获取闲置监视器数量的方法
闲置监视器指未被任何线程持有的监视器,目前没有现成的JDK命令直接输出,可通过以下方式估算或统计:
- 总监视器数减去被持有监视器数:先用上面的方法拿到总监视器数,再用
jcmd <进程ID> Thread.print -l或jstack <进程ID>解析输出,统计线程当前持有的监视器数量(找输出里locked <0x...>这类锁持有记录),两者相减得到闲置监视器的近似值。 - 自定义JVMTI工具统计:编写JVMTI代理程序,遍历所有对象并检查对象头的锁状态——处于无锁或偏向锁(无竞争)状态的对象,其对应的监视器属于闲置状态,直接统计这类对象的数量即可。
三、GC Safepoint "sync"阶段耗时过长的排查方向
结合你遇到的情况,sync阶段耗时久通常和线程进入safepoint的阻塞、JVM同步逻辑开销有关,可从这些角度排查:
- 验证闲置监视器的影响:如果统计后发现闲置监视器数量极大,JVM在safepoint同步时可能需要遍历大量监视器状态,增加了同步开销,可尝试优化锁使用减少监视器数量。
- 排查线程锁持有情况:用
jstack或jcmd Thread.print查看线程栈,找出长时间持有锁的线程——这类线程会拖慢其他线程进入safepoint的速度,进而拉长sync阶段时间。 - 调整JVM参数定位与优化:
- 开启详细safepoint日志:添加
-XX:+PrintSafepointStatistics -XX:PrintSafepointStatisticsCount=1,查看每个线程在safepoint阶段的耗时分布,精准定位拖慢sync的线程。 - 调整偏向锁参数:若偏向锁是诱因,可尝试关闭偏向锁(
-XX:-UseBiasedLocking),观察safepoint时间变化。 - 设置safepoint超时:添加
-XX:SafepointTimeout=5000 -XX:SafepointTimeoutDelay=1000,超时后JVM会打印拖慢safepoint的线程信息。
- 开启详细safepoint日志:添加
- 代码层面优化:检查业务代码是否存在大量不必要的锁使用,比如对频繁创建的临时对象加锁、锁粒度太粗等情况,这类问题会导致监视器数量激增,加重JVM同步负担。
内容的提问来源于stack exchange,提问作者Morten Knudsen
相关产品推荐
相关产品推荐

