安全点是jvm停顿执行gc等操作的检查点,配合jfr、安全点日志和火焰图可精准定位高并发长尾延迟:通过printsafepointstatistics发现nonvoluntary线程暴露阻塞问题;jfr启用safepointbegin/end、executionsample(1ms采样)等事件;火焰图聚焦深栈与高停顿交集,如couponruleengine中objectmapper递归序列化导致p99延迟飙升。

安全点(Safepoint)是 JVM 执行停顿以进行 GC、JIT 编译、线程 dump 等操作的检查点。它本身不是监控工具,但JVM 在安全点处采集的线程状态、栈帧信息与停顿时间,是定位高并发下长尾延迟最底层、最可信的物理依据。真正起作用的,是配合安全点机制启用的 JFR(Java Flight Recorder)+ 安全点日志 + 火焰图分析三者联动。
下面分三步讲清楚怎么用这套组合精准定位:
安全点日志开启与关键字段解读
先让 JVM 输出安全点相关事件,这是所有分析的基础:
- 启动参数加入:
-XX:+UnlockDiagnosticVMOptions -XX:+PrintGCDetails -XX:+PrintSafepointStatistics -XX:PrintSafepointStatisticsCount=1
- 关键输出示例(每秒一行):
Safepoint "ThreadDump", Time: 12.456 ms, VMopTime: 0.123 ms, Threads: 128, NonVoluntary: 42
其中
NonVoluntary: 42表示有 42 个线程因未及时到达安全点而被强制挂起——这直接暴露了线程正在执行长栈、死循环或阻塞 I/O,是长尾延迟的第一线索。
JFR 录制必须包含“栈深度”与“安全点停顿”事件
普通 JFR 配置容易漏掉关键维度。需自定义 recording template:
- 启用以下事件类型(通过
jcmd <pid> VM.native_memory summary</pid>或 JMC 配置):-
jdk.ThreadAllocationStatistics(定位内存分配热点) -
jdk.VirtualThreadPinned(虚拟线程 pinned 状态,说明在执行阻塞操作) -
jdk.SafepointBegin/jdk.SafepointEnd(精确测量每次 safepoint 停顿耗时) -
jdk.ExecutionSample(采样频率设为1ms,非默认10ms,否则错过短延时抖动)
-
- 录制命令示例:
jcmd <pid> VM.start_flight_recording \ name=longtail \ settings=profile \ duration=60s \ filename=/tmp/longtail.jfr \ compress=true</pid>
火焰图聚焦“深栈+高 safepoint 停顿”交集区域
导出 JFR 后,用 jdk.jfr 工具或 JMC 分析,重点看:
- 按
Event → SafepointEnd → Duration排序,找出Duration > 5ms的记录; - 对应的
stackTrace字段里,筛选出:- 调用深度 > 300 层的方法(如
OrderCalculator.calculate()连续嵌套调用); - 出现在
Log4j2Appender.append()、JacksonSerializer.serialize()、@Transactional代理链中的重复帧;
- 调用深度 > 300 层的方法(如
- 将这些栈帧导入火焰图(如
jfr-flame-graph),颜色越红、越宽,代表该路径既耗时又频繁触发 safepoint 停顿。
示例结论:某次压测中,
p99 延迟突增至 2.1s,JFR 显示 73% 的 safepoint 停顿集中在CouponRuleEngine.apply()方法,其平均栈深 482 层,且 92% 的样本卡在ObjectMapper.writeValueAsString()内部递归序列化 —— 直接锁定问题根因,而非猜测 CPU 或数据库慢。
不复杂但容易忽略。










