很多开发者学习 JVM 时:

知道GC原理
知道G1
知道ZGC
但到了线上:
CPU 100%
频繁Full GC
接口超时
服务卡死
OOM
却不知道如何排查。
实际上:
关注:
GC算法
垃圾收集器
JMM
关注:
为什么Full GC?
为什么OOM?
为什么CPU飙高?
为什么接口变慢?
真正值钱的是:
线上问题排查能力
先记住一句话:
没有最优参数
只有最适合业务的参数
例如:
电商系统:
低延迟优先
推荐:
G1
ZGC
批处理系统:
吞吐量优先
推荐:
Parallel GC
查看 Java 进程:
jps -l
输出:
12345 com.company.OrderApplication
67890 Jps
说明:
12345
就是目标进程 PID。
查看启动参数:
jinfo 12345
查看:
-Xms
-Xmx
GC配置
系统属性
查看 JVM 状态:
jstat -gc 12345 1000
每秒输出一次:
S0C
S1C
EC
OC
YGC
FGC
GC 日志记录:
什么时候GC
回收了多少内存
暂停了多久
例如:
[GC pause (G1 Evacuation Pause)
200M->50M(1024M)
10ms]
含义:
GC前:200MGC后:50M总堆:1024M耗时:10ms
JDK8:
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
-Xloggc:gc.log
JDK9+:
-Xlog:gc*:file=gc.log
生成:
gc.log
示例:
[GC (Allocation Failure)
256M->32M(512M),
0.010s]
分析:
256M
GC前使用32M
GC后使用512M
总堆大小10ms
GC耗时
重点关注:
GC频率
GC耗时
Full GC次数
例如:
每分钟Minor GC几次
正常。
如果:
每秒Minor GC
说明:
对象创建过快
如果:
频繁Full GC
通常:
内存配置不合理
或者:
内存泄漏
看到频繁GC:
先问:
对象太多?
内存太小?
泄漏?
不要直接改参数。
经典异常:
java.lang.OutOfMemoryError
OOM并不只有一种。
最常见。
异常:
java.lang.OutOfMemoryError:
Java heap space
示例:
List<byte[]> list =
new ArrayList<>();while(true){
list.add(new byte[1024*1024]);
}
持续占用堆。
最终:
Heap OOM
异常:
OutOfMemoryError:
Metaspace
常见原因:
动态生成大量类
例如:
CGLIB
ByteBuddy
ASM
异常:
StackOverflowError
示例:
public void test() {
test();
}
无限递归。
最终:
StackOverflowError
异常:
Direct buffer memory
示例:
ByteBuffer.allocateDirect(
1024 * 1024);
大量创建。
常见于:
Netty
NIO
生产环境必须开启:
-XX:+HeapDumpOnOutOfMemoryError
发生OOM:
自动生成:
heap.hprof
文件。
Heap Dump:
某一时刻
整个堆内存快照
包含:
所有对象
引用关系
对象大小
命令:
jmap -heap PID
查看:
堆大小GC配置各区域使用率
查看对象统计:
jmap -histo PID
输出:
num
instances
bytes
class name
例如:
1:
500000
40000000
java.lang.String
说明:
String过多
MAT:
Memory Analyzer Tool
JVM排查神器。
打开:
heap.hprof
重点看:
Leak Suspects
例如:
static HashMap
引用:
200万对象
立即发现问题。
错误代码:
public class Cache { private static final List<User>
USERS = new ArrayList<>();}
业务不断:
USERS.add(user);
由于:
static变量
始终是 GC Root。
导致:
永远不会回收
最终:
OOM
很多线上事故:
CPU直接100%
第一步:
top -Hp PID
查看线程。
输出:
12345
线程ID。
转16进制:
printf "%xn" 12345
例如:
3039
命令:
jstack PID > stack.log
搜索:
nid=0x3039
找到问题线程。
例如:
while(true){}
导致:
死循环
CPU100%。
代码:
synchronized(lock1){ synchronized(lock2){ }}
另一个线程:
synchronized(lock2){ synchronized(lock1){ }}
形成:
DeadLock
jstack:
jstack PID
直接输出:
Found one Java-level deadlock
现象:
接口越来越慢
监控:
Full GC
每分钟50次
jstat:
jstat -gc PID
发现:
Old区一直增长
Dump分析:
Order对象
300万
继续分析:
static Map<Long, Order>
缓存未清理。
解决:
Guava CacheRedis定时清理
问题解决。
设置初始堆:
-Xms4g
最大堆:
-Xmx4g
推荐:
Xms = Xmx
避免动态扩容。
G1停顿时间:
-XX:MaxGCPauseMillis=200
开启Dump:
-XX:+HeapDumpOnOutOfMemoryError
Dump路径:
-XX:HeapDumpPath=/data/dump
记住这张图。
出现问题:
服务变慢
↓
查看:
top
↓
CPU高?
YES
↓
jstack
分析线程。
CPU正常?
↓
查看:
jstat -gc
分析GC。
GC频繁?
↓
jmap -histo
查看对象。
↓
MAT
分析Dump。
↓
定位泄漏对象。
先看:
GC日志
再分析:
老年代对象
而不是直接调参数。
步骤:
开启Dump生成hprofMAT分析找到大对象
步骤:
toptop -Hpjstack定位线程
查看:
线程状态死锁阻塞
Leak SuspectsDominator Tree
-server-Xms4g
-Xmx4g-XX:+UseG1GC-XX:MaxGCPauseMillis=200-XX:+HeapDumpOnOutOfMemoryError-XX:HeapDumpPath=/data/dump-Xlog:gc*:file=/data/logs/gc.log
线上 JVM 排查核心思路:
服务异常
│
▼
CPU高?
│
├── YES
│ ▼
│ jstack
│
└── NO
▼
GC频繁?
│
▼
jstat
│
▼
Full GC?
│
▼
Dump
│
▼
MAT
│
▼
找到泄漏对象
到这里,你已经掌握了 JVM 的: