FullGC 如何排查
在生产环境中,Full GC 是 JVM 性能问题中最常见、最严重的问题之一。
典型表现:
- 服务 响应变慢
- CPU 突然飙高
- 接口超时
- 服务卡顿甚至假死
因此大厂通常有一套 标准化 FullGC 排查流程。
一、JVM 问题排查全景图
JVM问题
│
├─ GC问题
│ ├ jstat
│ ├ jmap
│ └ MAT
│
├─ CPU高
│ ├ top
│ └ jstack
│
├─ 线程问题
│ └ jstack
│
└─ 实时诊断
└ Arthas
二、JVM排查常用命令
| 命令 | 作用 |
|---|---|
jps -l | 查看 Java 进程 |
jstat -gcutil pid | 查看 GC 使用率 |
jmap -heap pid | 查看堆结构 |
jmap -histo pid | 查看对象数量 |
jmap -dump | dump 堆 |
jstack pid | 查看线程 |
jcmd pid GC.heap_info | 查看堆 |
jcmd pid GC.class_histogram | 对象统计 |
三、第一步:确认是否发生 Full GC
使用命令:
jstat -gcutil <pid> 1000
含义:
jstat -gcutil pid interval
| 参数 | 含义 |
|---|---|
| jstat | JVM 监控工具 |
| -gcutil | 查看 GC 内存使用率 |
| pid | Java 进程ID |
| interval | 采样间隔(ms) |
jstat 输出示例
S0 S1 E O M CCS YGC YGCT FGC FGCT GCT
0.00 0.00 20.35 80.12 92.14 88.23 1234 15.23 3 2.34 17.57
字段含义
| 字段 | 含义 |
|---|---|
| S0 | Survivor0 使用率 |
| S1 | Survivor1 使用率 |
| E | Eden 使用率 |
| O | Old 老年代使用率 |
| M | Metaspace |
| CCS | 压缩类空间 |
| YGC | Young GC 次数 |
| YGCT | Young GC 总时间 |
| FGC | Full GC 次数 |
| FGCT | Full GC 总时间 |
| GCT | GC 总时间 |
四、重点排查三个指标
1 Old区使用率(O)
O = Old区使用率
如果:
O > 90%
说明:
老年代即将满
马上可能 Full GC
2 Full GC 次数(FGC)
如果:
FGC 不断增长
例如:
1 → 2 → 3 → 4 → 5
说明:
Full GC 正在频繁发生
3 Full GC 耗时(FGCT)
FGCT = Full GC 总耗时
如果:
FGCT 快速增长
例如:
30s → 60s → 120s
说明:
Full GC 非常耗时
五、第二步:查看对象分布
执行:
jmap -histo:live <pid> | head -20
示例:
num #instances #bytes class name
1 1000000 800MB java.lang.String
2 500000 400MB HashMap$Node
说明:
String 占用内存过多
可能原因:
- 大量缓存
- Map未释放
- String拼接
六、第三步:查看堆结构
执行:
jmap -heap <pid>
示例输出:
Heap Configuration:
MaxHeapSize = 4096MB
NewSize = 125MB
OldSize = 333MB
NewRatio = 2
SurvivorRatio = 8
含义
| 参数 | 含义 |
|---|---|
| MaxHeapSize | 最大堆 |
| NewSize | 新生代 |
| OldSize | 老年代 |
| NewRatio | 新生代:老年代 |
| SurvivorRatio | Eden:Survivor |
七、第四步:Dump堆内存
执行:
jmap -dump:live,format=b,file=heap.hprof <pid>
生成文件:
heap.hprof
这个文件包含:
JVM 所有对象
八、第五步:使用 MAT 分析
打开工具:
Eclipse MAT
导入:
heap.hprof
MAT 排查口诀
Histogram
↓
大对象
↓
Dominator Tree
↓
谁持有对象
↓
Path to GC Root
↓
代码
1 Histogram
查看:
对象数量排行
例如:
java.lang.String
HashMap
byte[]
2 Dominator Tree
查看:
谁占用最多内存
3 Path to GC Root
查看:
谁引用了对象
找到:
真正内存泄漏代码
九、CPU 100% 排查
第一步:找CPU线程
top -Hp <pid>
输出:
PID TID %CPU
1000 3456 99%
说明:
线程 3456 CPU占用最高
第二步:转16进制
printf "%x\n" 3456
输出:
d80
第三步:找线程栈
jstack <pid> | grep d80
找到:
nid=0xd80
看到代码:
OrderService.calculate()
说明:
这里CPU100%
十、线程问题排查
执行:
jstack <pid>
示例:
"main" #1 prio=5 os_prio=0 tid=0x0000000001a0a000 nid=0x3b4 runnable
java.lang.Thread.State: RUNNABLE
at com.demo.OrderService.createOrder(OrderService.java:50)
字段说明
| 字段 | 含义 |
|---|---|
| main | 线程名 |
| prio | 优先级 |
| tid | JVM线程ID |
| nid | OS线程ID |
| runnable | 状态 |
线程状态
| 状态 | 含义 |
|---|---|
| RUNNABLE | 运行 |
| BLOCKED | 等待锁 |
| WAITING | 等待 |
| TIMED_WAITING | 定时等待 |
| NEW | 新建 |
| TERMINATED | 结束 |
十一、死锁识别
如果死锁:
Found one Java-level deadlock
示例:
Thread-1 等待 Thread-2
Thread-2 等待 Thread-1
十二、Arthas 实时诊断
下载
curl -O https://arthas.aliyun.com/arthas-boot.jar
或
wget https://arthas.aliyun.com/arthas-boot.jar
启动
java -jar arthas-boot.jar
输出:
* [1]: 1000 order-service.jar
选择:
1
进入:
[arthas@1000]$
十三、Arthas 常用命令
| 命令 | 作用 |
|---|---|
| dashboard | JVM整体状态 |
| thread | 查看线程 |
| jvm | JVM信息 |
| memory | 内存 |
| gc | GC情况 |
| sc | 查看类 |
| sm | 查看方法 |
| watch | 方法参数 |
| trace | 方法耗时 |
| stack | 调用栈 |
dashboard
dashboard
可以看到:
- CPU
- Heap
- Thread
- GC
thread
查看线程:
thread
查看 CPU 高线程:
thread -n 5
memory
查看内存:
memory
示例:
Heap used: 5GB
max: 8GB
trace(排查慢接口)
trace com.example.OrderService createOrder
输出:
createOrder()
+---validateUser() 5ms
+---queryProduct() 20ms
+---saveOrder() 150ms
说明:
saveOrder慢
十四、Full GC 排查完整流程
1 确认GC
jstat -gcutil
2 查看对象
jmap -histo
3 查看堆结构
jmap -heap
4 dump内存
jmap -dump
5 MAT分析
Dominator Tree
Path to GC Root
6 实时分析
Arthas