目录
- Q1 工具矩阵与一行用途速查
- Q2
jcmd
常用子命令与范式 - Q3
jps
用法与注意事项 - Q4
jmap
选项(-F/-dump/-histo/-heap)与风险 - Q5
jstat -gcutil
各列含义(含 M vs P、CCS) - Q6 用
jstat
判断内存问题:采样节奏与误区 - Q7
Thread.print
/jstack
:高 CPU/死锁/IO 阻塞判读 - Q8
top -H
+Thread.print
SOP 与 TID→nid 映射 - Q9 直接内存(DirectByteBuffer)生命周期与释放误区
- Q10 采样侵入性与频率:哪些命令会 STW
- Q11 120 秒快速体检脚本(基线)
- Q12 堆外/本地内存三步闭环
- Q13 DirectByteBuffer 疑似泄漏:确认→止血→修复
- Q14 仅用 JFR/JMC 2–3 分钟捕捉 CPU/分配热点
- Q15 高 CPU + 频繁 Young GC:三段式排查
- Q16 专治短命对象暴增的 Checklist
- Q17 GC 日志(JDK8/11+)开启与“是否 GC 绑定”判读
- Q18 Code Cache 逼近 & 容器注意点(扩展)
- Q19
hs_err_pid
崩溃日志快速判读 - Q20 OOM 取证与快速处置
- Q21 GC 选型与参数基线:G1 vs ZGC
- Q22 线程 Dump 状态判读与 TID→nid 映射
- Q23 Heap Dump 精确分析:最小流程
- Q24 ClassLoader 泄漏定位与修复
- Q25 统一日志量化 5 分钟 GC 占比
- Q26 Safepoint/TtS 量化与处置
- Q27 NMT(原生内存)三步法与替代方案
- Q28 JFR 预设与低开销取证
- Q29 TLAB / TLAB 外分配 监控与优化
- Q30 对象年龄分布 / TenuringDistribution 判读
- Q31
VM.flags
/VM.command_line
与可热改项 - Q32 统一日志选择器速查与窗口聚合
- Q33
Compiler.directives
JIT 应急绕路 - Q34 JIT/CodeCache 体检与编译队列
- Q35 综合演练:2 分钟从症状到证据
- Q36 年龄分布 + 统一日志识别早晋升/撑爆
- Q37 收官:早晋升 + Outside TLAB 联合诊断
Q1|工具矩阵与一行用途速查
- jcmd:统一诊断入口;线程/GC/堆/参数/JFR/NMT 等一站式命令。
- jstat:轻量采样 GC/类加载/编译等运行时统计(低侵入)。
- jstack / jcmd Thread.print:导出线程快照,查死锁/高 CPU/卡顿热点栈。
- jmap:堆转储(dump)与类直方图(histo);线上慎用 dump。
- jinfo / VM.flags / VM.command_line:查看/少量可热改的 JVM 标志与启动参数。
- jps:列出本机 Java 进程(PID、MainClass),定位目标进程。
- jhsdb:基于 SA 的离线剖析(进程挂死/无法附加时)。
- JFR / JMC:低开销事件级录制与分析(飞行记录器 + 控制台)。
- (可选)JConsole / VisualVM:图形化监控与基础分析。
Q2|jcmd
常用子命令与范式
jcmd <PID> Thread.print # 线程快照(死锁/热点栈)
jcmd <PID> GC.heap_info # 堆概览(各代 used/committed)
jcmd <PID> GC.class_histogram # 类直方图(低频、会进安全点)
jcmd <PID> GC.heap_dump filename=/path.hprof # 堆转储(低峰执行)
jcmd <PID> GC.run # 触发一次 GC(仅买时间)
jcmd <PID> VM.flags # 查看标志;VM.command_line 启动参数
jcmd <PID> VM.system_properties # 系统属性(时区/编码等)
jcmd <PID> JFR.start|JFR.check|JFR.dump|JFR.stop # 录制/导出/停止 JFR
jcmd <PID> VM.native_memory baseline|summary|summary.diff # NMT(三步法,需启动开启)
jcmd <PID> VM.log what=... # 统一日志在线开启/关闭
范式:低侵入优先;
class_histogram/heap_dump
低频、在低峰;JFR 2–3 分钟快速取证。
Q3|jps
用法与注意事项
- 用法:
jps -l
显示主类/全限定名;jps -v
带 JVM 参数;jps -m
带 main 参数。 - 注意:
jps
仅列进程,不显示内存/类加载数据;跨容器/权限受限环境可能不全。
Q4|jmap
选项与风险(-F/-dump/-histo/-heap)
-dump:format=b,file=…[,live]
:生成堆 dump;live
仅活对象(耗时更长)。风险:长 STW/IO 大,线上需低峰+充足磁盘。-histo[:live]
:按类统计#instances/#bytes
;会进安全点,低频使用(≥20s 一次)。-heap
:打印堆与 GC 概览;-F
:强制附加(进程卡死时),风险更大,仅兜底使用。
Q5|jstat -gcutil
各列含义(含 M vs P、CCS)
S0/S1
:Survivor0/1 使用率(%);E
:Eden 使用率(%);O
:Old 使用率(%);M
:Metaspace 使用率(JDK8 之后取代旧版P
PermGen);CCS
:Compressed Class Space 使用率(%);YGC/YGCT
:Young GC 次数 / 累计耗时(秒);FGC/FGCT
:Full GC 次数 / 累计耗时(秒);GCT
:GC 总耗时(秒)。
判读要点:看 斜率(ΔGCT/Δt)、是否回落(E/S 清空频率、O 是否上行)与 YGC/FGC 的节奏。
Q6|用 jstat
判断内存问题:采样节奏与误区
-
节奏:常用
jstat -gcutil <PID> 1000 60
(1s×60);高压可到 500ms;避免过短造成噪声。 -
判据:
- 频繁 Young GC:
YGC
快涨、E/S
高频充满→清空、YGCT
斜率高; - 老年代不回落:多次(F)GC 后
O
仍上行; - GC 绑定:5 分钟窗口 GC 占比 ≥30%(结合统一日志计算)。
- 频繁 Young GC:
-
误区:仅靠
jstat
无法看到堆外;也看不到分配来源/类型(需 JFR/直方图)。
Q7|Thread.print
/jstack
:高 CPU/死锁/IO 阻塞判读
- 高 CPU:多次采样(相隔 1–2s)同一批 RUNNABLE 栈稳定在计算/序列化/正则 → 计算热点。
- 伪 RUNNABLE:
socketRead0/epollWait
等 I/O 原生调用阻塞但显示 RUNNABLE。 - 死锁:输出末尾 Deadlock 区段或大量
waiting to lock <0x…>
; - 快捷映射:
top -H -p <PID>
取 TID →printf '0x%x\n' <tid>
→ 在线程 dump 中搜nid=0x…
。
Q8|top -H
+ Thread.print
SOP 与 TID→nid 映射
# ① 找高CPU线程
top -H -p <PID> | head
# ② TID 十进制→十六进制
printf '0x%x\n' <tid>
# ③ 抓两次线程栈对比
jcmd <PID> Thread.print > /tmp/th1.txt ; sleep 2 ; jcmd <PID> Thread.print > /tmp/th2.txt
# ④ 定位相应 nid 的栈段
sed -n '/nid=0x<hex>/,/^$/p' /tmp/th2.txt
看点:计算热点/锁竞争/IO 阻塞;配合 JFR Hot Methods/Locks 进一步确认。
Q9|直接内存(DirectByteBuffer)生命周期与释放误区
- 不归属线程:Direct 内存由包装对象/引用计数(如 Netty)管理,非“哪个线程创建就属于谁”。
- 释放机制:对象不可达后经 Cleaner 在线程中调用
freeMemory
,需等 GC/引用处理;线程结束≠释放。 - 池化延迟:池化/线程缓存会延迟归还;可能不立刻还给 OS。
- 当场措施:限流降并发;(Netty)trim 线程缓存;
GC.run
仅回收已不可达;JMX 观测BufferPool: direct
。
Q10|采样侵入性与频率
- 可能进入安全点(有停顿):
Thread.print
、GC.class_histogram
、heap_dump
(最重)、jmap -F
(极重)。 - 低侵入:
jstat
、JFR(settings=default/profile
)、统一日志VM.log
、GC.heap_info
。 - 建议:直方图 ≥20–30s 一次;dump 选低峰;JFR 2–3 分钟短录优先。
Q11|120 秒快速体检脚本(基线)
PID=<PID>
jstat -gcutil $PID 1000 120 > /tmp/gcutil-120s.log &
jcmd $PID Thread.print > /tmp/th1.txt ; sleep 2 ; jcmd $PID Thread.print > /tmp/th2.txt &
jcmd $PID JFR.start name=triage settings=profile duration=180s filename=/tmp/triage.jfr dumponexit=true
读法:
jstat
看 YGC/ΔGCT 与 E/S/O;Thread.print
看计算/锁/IO;JFR 后续在 JMC 看 Hot Methods & Allocation。
Q12|堆外/本地内存三步闭环
① 判断“Java 堆稳定但 RSS 上涨”
- 堆/GC:
jstat -gcutil <PID> 2000 10
;jcmd <PID> GC.heap_info
。 - 进程 RSS:
cat /proc/<PID>/status | egrep 'VmRSS|VmSwap'
;cat /proc/<PID>/smaps_rollup
;pmap -x <PID> | sort -k3 -n | tail
。
→ 若堆占用/GCT 斜率小而 RSS/Swap 上行,怀疑堆外。
② NMT 定位(需启动 -XX:NativeMemoryTracking=summary|detail
)
jcmd <PID> VM.native_memory baseline
# 等待 1–2 分钟
jcmd <PID> VM.native_memory summary.diff
关注:Thread(线程栈)、Class(Metaspace)、Code、Internal/Other(常见 Direct/JNI)。
③ 未开 NMT 的替代(2+)
- RSS vs 堆:
jstat
+/proc/...
侧证堆外; - JFR 短录看 DirectBuffer/IO/线程创建;
- JMX BufferPool:
java.nio:type=BufferPool,name=direct
; - 线程数与
ulimit -s
估算 Thread committed。
Q13|DirectByteBuffer 泄漏:确认→止血→修复
确认(2+ 证据)
- JMX BufferPool
direct
:MemoryUsed/TotalCapacity/Count
单调上升; - NMT
summary.diff
:Internal/Other
增长; - RSS vs 堆:堆稳而 RSS 涨。
止血(不重启)
- 限流/降级,压低分配速率;
- 一次性 GC:
jcmd <PID> GC.run
(仅对不可达 DirectBuffer 有效); - (Netty)trim 线程本地缓存(版本相关)。
中期修复
- 参数:合理由上限
-XX:MaxDirectMemorySize
(重启);用 JFR 取证与(Netty)LeakDetector; - 代码:严格
release()
;改为堆内/分块/流式;限制最大报文。
Q14|仅用 JFR/JMC 2–3 分钟捕捉 CPU/分配热点
命令序列(JDK11+)
jcmd <PID> JFR.start name=triage settings=profile duration=180s \
stackdepth=128 samplethreads=true filename=/tmp/triage.jfr dumponexit=true
jcmd <PID> JFR.check
jcmd <PID> JFR.dump name=triage filename=/tmp/triage-snap.jfr
jcmd <PID> JFR.stop name=triage
JMC 先看 5 个视图:Hot Methods / Allocation(TLAB & Outside) / GC / Locks / I/O。
处置:
- 计算热点→优化算法/缓存/拆批;
- 分配过猛→对象复用/流式化/限流;必要时调整 Young(重启)。
Q15|高 CPU + 频繁 Young GC:三段式排查
1) 快速体检
top -H -p <PID>
(10–20s):记高 CPU TID;jstat -gcutil <PID> 1000 20
:看 ΔGCT、YGC、E/S0/S1 波动;jcmd <PID> Thread.print
×2(相隔 2s):定位热点线程栈。
2) 根因定位
- 计算热点:RUNNABLE 栈稳定在计算/序列化;ΔGCT 小;
- 短命对象:YGC 快增、E/S0/S1 频清空;Histogram/JFR 分配速率高。
3) 缓解→修复
- 不重启:限流/降级;压缩本地缓存;短录 JFR;
- 重启后:增大新生代、调
MaxTenuringThreshold
;代码复用/流式。
Q16|短命对象暴增 Checklist(定位→改善→复验)
定位(低侵入):jstat
时间序列;GC.heap_info
;JFR Allocation;低频 GC.class_histogram
;对齐网关指标;核对 VM.flags
;查看 GC 日志;排除堆外。
改善(代码):减少临时对象;集合预尺寸;避免装箱;JSON/XML 流式;分页/分块;合理缓存;日志降采样;规范 ThreadLocal。
配置(重启项):增大新生代;调 MaxTenuringThreshold
;TLAB 评估;
运行期缓解:限流/降级。
复验:jstat
YGC/ GCT 改善;JFR 分配热点下降;直方图 Top-K 不再上升;SLO 恢复。
Q17|GC 日志(JDK8/11+)开启与判读
JDK8 最小参数:
-XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps
-Xloggc:/var/log/gc.log -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=20M
# (可选)-XX:+PrintTenuringDistribution
JDK11+ 运行期开启/导出:
jcmd <PID> VM.log what=gc*,safepoint decorators=uptime,level,tags \
filename=/var/log/gc.log filecount=5 filesize=20M
# 停止:
jcmd <PID> VM.log disable
首看 3 信号:暂停占比;频度/斜率;回收后占用是否回落(若 FGC 后不降 → 泄漏/老年代压力)。
Q18|Code Cache 逼近 & 容器注意点(扩展)
证据:
-Xlog:codecache=info
/VM.log what=codecache=info
:使用率/满;- NMT
Code committed
接近ReservedCodeCacheSize
。
止血(在线):Compiler.directives_add
排除热点方法或禁 C2,放缓产出。
(扩展)容器注意点:容器感知堆百分比;PID/命名空间取证差异;时区与日志卷一致性(本点属章节外延,保留为附录)。
Q19|hs_err_pid
崩溃日志快速判读
先看 6 段:错误头部与信号;Problematic frame;Current thread(含 Stack 区间);Native/Java frames;VM Arguments;内存与 CodeCache 摘要。
归因判据:
- JVM Bug:Problematic frame 在
libjvm.so
且伴内部断言; - 第三方 JNI:Problematic frame 指向
.so/.dll
; - CodeCache:
CodeCache is full
或使用率逼近; - 栈溢出:Stack 贴近保护页/
SOE
; - Metaspace:使用逼近上限/日志含
Metadata GC Threshold
; - 直接内存:
Direct buffer memory
/mmap
失败痕迹。
当场止血:限流/降级;JIT directives 绕行;(Direct)trim/限流;暂停外部高频 ThreadDump/重定义。
重启参数:扩大 CodeCache / 栈 / Metaspace / Direct 上限;或退编译级别(C2→C1/解释)。
Q20|OOM 取证与快速处置
启动期取证参数:
-XX:+HeapDumpOnOutOfMemoryError
、-XX:HeapDumpPath=...
-XX:+ExitOnOutOfMemoryError
、-XX:OnOutOfMemoryError="..."
-XX:ErrorFile=/var/log/app/hs_err_pid%p.log
- JDK8:
-Xloggc
+ 滚动;可选 JFR(Oracle JDK8)。 - JDK11+:
-Xlog:gc*,safepoint:...
;-XX:StartFlightRecording=...
事发后 60 秒动作(低侵入优先):
- 在线开启统一日志;
jstat -gcutil
;GC.heap_info
; - 低频
GC.class_histogram
; JFR.start
120s;top -H
+Thread.print
双采;- 业务限流;(极端)
GC.run
或 HeapDump(低峰)。
分类型止血/修复:
- Heap space/GC overhead:限流/降缓存;增大堆/新生代;代码减分配。
- Metaspace:停热更/修 ClassLoader;增
MaxMetaspaceSize
。 - Direct:限流/trim;设
MaxDirectMemorySize
;代码释放/流式化。 - unable to create new native thread:降并发/提
ulimit -u
;调小-Xss
与线程模型优化。 - CodeCache(可选):directives 绕行;扩大
ReservedCodeCacheSize
。
Q21|GC 选型与参数基线:G1 vs ZGC
选型要点(≥6):堆大小;暂停目标;吞吐 vs 延迟;JDK 成熟度;碎片/压实;内存弹性(SoftMaxHeapSize/ZUncommit);容器配额;调参复杂度。
G1 基线(容器):
-XX:+UseContainerSupport
-XX:InitialRAMPercentage=40 -XX:MaxRAMPercentage=70
-XX:+UseG1GC -XX:MaxGCPauseMillis=200
-XX:InitiatingHeapOccupancyPercent=30
-XX:G1NewSizePercent=20 -XX:G1MaxNewSizePercent=60
-XX:G1ReservePercent=20 -XX:+ParallelRefProcEnabled
-XX:ActiveProcessorCount=<核数>
-Xlog:gc*,safepoint:file=/var/log/gc.log:time,uptime,level,tags:filecount=5,filesize=20M
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/app
-XX:ErrorFile=/var/log/app/hs_err_pid%p.log
ZGC 基线(JDK17+):
-XX:+UseZGC -XX:+ZUncommit
-XX:SoftMaxHeapSize=<size/%> -XX:ZUncommitDelay=300s
-XX:InitialRAMPercentage=40 -XX:MaxRAMPercentage=70
-XX:ActiveProcessorCount=<核数>
-Xlog:gc*,safepoint:file=/var/log/gc.log:time,uptime,level,tags:filecount=5,filesize=20M
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/app
-XX:ErrorFile=/var/log/app/hs_err_pid%p.log
上线验收(ZGC):Pause P99 ≤10–20ms;无明显 Allocation Stall;GC 线程 CPU <5–10%,RSS 随闲忙回落。
Q22|线程 Dump 状态判读与 TID→nid 映射
状态特征(各2个):
- RUNNABLE:计算热点方法;或 I/O 原生调用伪 RUNNABLE。
- BLOCKED:
waiting to lock
、ReentrantLock/AQS.acquire
。 - WAITING/TIMED_WAITING:
Object.wait
/Condition.await
、Thread.sleep
。 - PARKED:
LockSupport.park
、ForkJoinPool.awaitWork
。 - NATIVE:
read0/write0/epollWait/accept0
、JNI/Unsafe.copyMemory
。
根因与处置:CPU 热点优化/限流;锁竞争缩临界区/分段锁;队列/背压;I/O 设超时与熔断。
TID→nid 映射:top -H -p <PID>
→ printf '0x%x\n' <tid>
→ 在 Thread.print
搜 nid=0x...
。
Q23|Heap Dump 精确分析:最小流程
何时抓:FGC 后 Old 不回落;Histogram Top-K 3 次上升;Metaspace 高位;选低峰(会 STW/大 IO)。
怎么抓:
jcmd <PID> GC.heap_dump filename=/data/dump/app-$(date +%F-%H%M).hprof
# 兜底:jmap -dump:format=b,file=... <PID> (-F 谨慎)
看什么:Dominator Tree(最大保留)/ Path to GC Roots(强引用链)/ Leak Suspects。
修复与复验:移除静态强引用/缩缓存/ThreadLocal remove/流式化;jstat
斜率改善、Histogram 不再上行、SLO 恢复。
Q24|ClassLoader 泄漏定位与修复
线上信号:
- 堆:
*ClassLoader
Retained 大、强引用链指向静态/线程/ThreadLocal; - NMT/统计:
Class(Metaspace)
上涨;jstat -class
Loaded↑/Unloaded↓; - 现象:
Metadata GC Threshold
频繁 / OOME: Metaspace。
MAT/JMC 三步:找 Loader → 看到根路径/支配关系 → 落到具体字段/集合/重复类。
修复:URLClassLoader#close()
与停后台线程;断开静态回指与缓存隔离;控制动态生成类/代理;ThreadLocal 规范。增大 MaxMetaspaceSize
仅买时间。
Q25|统一日志量化 5 分钟 GC 占比
开启:-Xlog:gc*,safepoint:...:uptime,level,tags
或 jcmd VM.log what=gc*,safepoint ...
。
awk 思路:累计最近 300s 内 Pause ... <xx>ms
为 GC 秒数,ratio=gc_sec/300
;可同时统计事件次数、Safepoint。
非 GC 绑定时的初判:jstat -gcutil <PID> 1000 60
(ΔGCT≈0);Thread.print
×2 判 CPU/锁。
Q26|Safepoint/TtS 量化与处置
方法 A(快照差分):
jcmd <PID> VM.print_safepoint_statistics -verbose > /tmp/sp1.txt
sleep 300
jcmd <PID> VM.print_safepoint_statistics -verbose > /tmp/sp2.txt
# 计算 ΔTtS/ΔStopped 占比;聚合 VM Operation Top-N
方法 B(-Xlog:safepoint):窗口聚合 stopped
与 time to safepoint
。
高 TtS 三类根因:JNI/系统调用卡住;长计算循环缺轮询点;GC Locker/JNI Critical 区域。对应:限流/缩短 native 段/加入轮询点;必要时 GuaranteedSafepointInterval
(治标)。
Q27|NMT(原生内存)三步法与替代方案
开关与开销:-XX:NativeMemoryTracking=summary|detail
;summary ~1–2% 开销、detail 更高,不宜长开。
三步:baseline
→ 等待 → summary.diff
;看 Thread / Class(Metaspace) / Code / Internal(Other) 等类目。
典型场景:Thread 涨→下调并发/排查泄漏;Class 涨→停热更/查 Loader 泄漏;Internal/Other 涨→Direct/JNI,限流+trim。
若未开 NMT:RSS vs 堆对比;JMX BufferPool/线程数/CodeCache 侧证(有局限)。
Q28|JFR 预设与低开销取证
预设:default
(长期巡检,~0.5–1%)/profile
(短时热点,~1–3%)/continuous
(有的发行版提供,接近 default)。
短时取证(180s):JFR.start name=triage settings=profile duration=180s stackdepth=128 samplethreads=true filename=/tmp/triage.jfr
。
低扰动巡检(≥30m):settings=default stackdepth=64 samplethreads=false maxage=30m maxsize=256m
,周期 dump
,控制磁盘占用。
上线验收:GC Pause P99 增加 <5ms/5%;CPU 增幅 <2%,QPS/P99 变化 ❤️–5%;Allocation/锁等待无显著上扬。
Q29|TLAB / TLAB 外分配 监控与优化
概念:TLAB 内 = 线程本地快分配;Outside TLAB = 慢路径(对象大/剩余不足/逃逸)。
观察:JFR 的 Allocation in/outside TLAB
;或 VM.log what=gc+tlab=debug
;JDK8 可 -XX:+PrintTLAB
(重启)。
Outside 高的原因→动作:
- 对象过大 → 分块/流式/复用缓冲;
- TLAB 不匹配 → 依赖
ResizeTLAB
,必要时评估 Young/MinTLABSize
(重启); - 跨线程发布/逃逸 → 在消费线程内创建与复用;
- 临时对象过多 → 复用 Builder/数组、预尺寸集合。
快速判定:JFR 180s;Outside 字节占比 >20–30% 且 YGC 频繁 → 倾向代码整改或 TLAB/Young 调优。
Q30|对象年龄分布 / TenuringDistribution 判读
开启:JDK8:-XX:+PrintTenuringDistribution
(配 GC 日志);JDK11+:-Xlog:gc+age=trace
或运行期 VM.log
开启。
早晋升:new threshold ≤ 2
频现;原因:Survivor 小/阈值低/存活率高;动作:扩大新生代/Survivor、适度提高 MaxTenuringThreshold
(压测)。
Survivor 撑爆:desired survivor 远小于实际存活、甚至 promotion failure;原因:瞬时存活峰值高/Outside 多;动作:扩大新生代/Survivor,代码侧削峰。
体检脚本:1 分钟每 5s 抽取年龄日志;配 jstat
观察 YGC/ΔGCT 斜率,判定是否需要调阈值/比例。
Q31|VM.flags
/VM.command_line
与可热改项
查看:VM.command_line
(原始参数)、VM.flags
(标志与可写属性)、VM.system_properties
。
可在线操作:统一日志 VM.log
、JFR、编译指令 Compiler.directives_*
、极少数 manageable 标志(如 MinHeapFreeRatio
)。
不可在线改:-Xms/-Xmx
、收集器选择、大多数堆布局、Metaspace/Direct/CodeCache 上限、NMT 开关等。
Q32|统一日志选择器速查与窗口聚合
常用选择器:gc*=info
、gc+phases=debug
、gc+heap=info
、gc+age=trace
、gc+tlab=debug
、gc+metaspace=info
、safepoint=info
、codecache=info
。
运行期开启/关闭:jcmd <PID> VM.log what=gc*,safepoint ... filename=...
;或 output=stdout
即席;关闭 VM.log disable
。
5 分钟窗口聚合:用 uptime
装饰器统计 Pause
总时长/次数、GC 占比、new threshold
最小值(awk/grep)。
阈值与动作:暂停占比 ≥30% 或 5 分钟内 Full GC ≥2 次 → 限流/暂停重任务;必要时一次性 GC.run
(低峰)。
Q33|Compiler.directives
JIT 应急绕路
操作:
jcmd <PID> Compiler.directives_add '[{"match":"com.foo.Bar::hot","Exclude":true}]'
jcmd <PID> Compiler.directives_add '[{"match":"com.foo.*","C1":true,"C2":false}]'
jcmd <PID> Compiler.directives_print
jcmd <PID> Compiler.directives_remove <ID> # 或 Compiler.directives_clear
效果:被 Exclude 的方法退回解释执行;禁 C2 则最多到 C1,缓解 CodeCache/编译崩溃路径。
触发条件:CodeCache 逼近/队列堆积;疑似 JIT 相关崩溃(hs_err
/CompileTask)。
回滚与验证:directives_remove/clear
;开 -Xlog:compilation=debug
/VM.log
验证;看 Compiler.queue
与 codecache=info
曲线回落。
Q34|JIT/CodeCache 体检与编译队列
体检命令:Compiler.queue
看队列;-Xlog:compilation=debug
看洪峰;-Xlog:codecache=info
看使用率/满告警。
不重启止血:directives 排除热点或禁 C2(性能下降);业务限流(SLA 受限)。
重启修复:增大 ReservedCodeCacheSize
;降低编译强度(TieredStopAtLevel
/-UseC2
)。
为何放大延迟:编译线程争用 CPU + 安装/去优化触发安全点与指令缓存失效,热点退回解释,尾延迟上扬。
Q35|综合演练:2 分钟从症状到证据
采集脚本(120s):
PID=<PID>
jstat -gcutil $PID 1000 120 > /tmp/gcutil-120s.log &
jcmd $PID Thread.print > /tmp/th1.txt ; sleep 2 ; jcmd $PID Thread.print > /tmp/th2.txt &
jcmd $PID JFR.start name=triage settings=profile duration=180s filename=/tmp/triage.jfr dumponexit=true
判定:
- 计算热点:ΔGCT 小;RUNNABLE 栈稳定在计算;JFR Hot Methods 高、Allocation 不夸张。
- 短命对象:YGC 快增、E/S 频清空、YGCT 斜率高;JFR Allocation/TLAB 热。
动作:限流/降级;收缩缓存;重启后增新生代/适度提晋升阈值,代码复用/流式。
Q36|统一日志 + 年龄分布 → 识别“早晋升 / Survivor 撑爆”
开启:jcmd <PID> VM.log what=gc+age=trace decorators=uptime,level,tags filename=/var/log/gc-age.log
;关闭 VM.log disable
。
统计:最近 5 分钟 new threshold
最小值;判定“早晋升”阈值:≤2 多次出现。
Survivor 撑爆:不重启:限流/降批;一次性 GC.run
(低峰,取证为主)。重启:增新生代/Survivor;适度提 MaxTenuringThreshold
(压测)。
Q37|收官:2 分钟识别“早晋升”与“TLAB 外分配”
采集(120s):jstat -gcutil
+ VM.log gc+age=trace
+ JFR.start 180s
。
早晋升信号:年龄日志 new threshold ≤ 2
;jstat
中 O
随 YGC 上行、E/S 频清零。
Outside TLAB >30% 时:
- 代码:分块/流式/复用缓冲;在消费线程内创建并复用对象(减少逃逸)。
- 参数(重启):适度增大新生代/Survivor(G1
G1NewSizePercent/G1MaxNewSizePercent
;评估MinTLABSize
)。