怎么分析JVM当前的内存占用情况?OOM后怎么分析?
重点
首先需要理解JVM的内存组成,参考:JVM的内存区域是如何划分的?
分析JVM内存可以用jstat和jmap这两个命令:
- jstat能实时监控堆内存、GC情况,适合看活跃状态
- jmap能生成堆快照,拿到详细的对象分布信息
实时监控流程:
- 用
jstat -gc <pid> 1000 10每1秒打印一次内存使用情况,连续观察10次 - 重点关注Eden、Old区的使用率,以及FGC的频率
- 如果FGC频率很高,说明老年代快撑不住了
OOM后定位流程:
- 用jmap生成堆转储文件,或者提前加上JVM参数
-XX:+HeapDumpOnOutOfMemoryError让系统自动dump - 把dump文件导入MAT或VisualVM这些工具
- 找出占用内存最多的对象,追溯它的引用链
- 定位到具体代码,看是缓存没清理还是集合无限增长
// 添加 JVM 启动参数,OOM 时自动 dump
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/tmp/heapdump.hprof
// 手动生成堆转储文件
jmap -dump:format=b,file=heap_dump.hprof <pid>
// 实时监控内存
jstat -gc <pid> 1000 10
扩展
jps
jps显示用户所有java进程的PID和主类名称
jstat 监控内存
jstat是JDK自带的轻量级工具,不会堆应用造成太大影响。
最常用的是-gc 和 -gcutil两个选项,前者显示字节数,后者显示百分比。
jstat -gc <pid> 1000 10
参数含义:
- -gc:显示GC相关信息
<pid>:Java 进程ID- 1000: 每1000毫秒采样依次
- 10:采样10次
输出示例:
S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT
1536.0 1536.0 0.0 0.0 30720.0 1024.0 708608.0 2048.0 44800.0 43712.6 4864.0 4096.0 4 0.072 1 0.015 0.087
核心字段解读:
- S0C/S1C:两个Survivor区的容量
- S0U/S1U:两个Survivor区的使用量
- EC/EU:Eden区的容量和使用量
- OC/OU:老年代的容量和使用量
- MC/MU:原空间的容量和使用量
- YGC/YGCT:Young GC的次数和总耗时
- FGC/GGCT:Full GC的次数和总耗时
重点关注FGC的变化频率,如果这个数字一直在涨,说明老年代压力很大,可能要OOM了。
一般来说,生产环境FGC频率应该控制在小时级别,如果是分钟级别触发就得赶紧排查。
jmap 查看对详情
jmap 能生成堆快照,也能直接看当前堆的配置和使用情况。注意这个命令会导致应用暂停,特别是大堆,可能会卡几秒,生产环境要慎用。
jmap -heap <pid>
输出示例:
Heap Configuration:
MinHeapFreeRatio = 40
MaxHeapFreeRatio = 70
MaxHeapSize = 1048576000 (1000.0MB)
NewSize = 1310720 (1.25MB)
MaxNewSize = 17592186044415 MB
OldSize = 8388608 (8.0MB)
NewRatio = 2
SurvivorRatio = 8
MetaspaceSize = 21807104 (20.796875MB)
CompressedClassSpaceSize = 1073741824 (1024.0MB)
Heap Usage:
Eden Space:
capacity = 41943040 (40.0MB)
used = 12058624 (11.5MB)
free = 29884416 (28.5MB)
28.77% used
Old Generation:
capacity = 100663296 (96.0MB)
used = 1433600 (1.37MB)
free = 99229696 (94.63MB)
1.42% used
这个输出能看到堆的配置参数和各个区域的使用情况,如果发现Old Generation使用率超过80%,就得警惕了。
arthas实时监控
arthas是阿里开源的Java诊断工具,比jstat更友好。输入dashboard命令就能看到所有线程、内存、GC的实时信息,按ctrl+c退出。
$ dashboard
Memory used total max usage GC
heap 32M 155M 1820M 1.77% gc.ps_scavenge.count 4
ps_eden_space 14M 65M 672M 2.21% gc.ps_scavenge.time(ms) 166
ps_survivor_space 4M 5M 5M
ps_old_gen 12M 85M 1365M 0.91% gc.ps_marksweep.count 0
nonheap 20M 23M -1 gc.ps_marksweep.time(ms) 0
code_cache 3M 5M 240M 1.32%
arthas的优势是不用记参数,直接可视化展示,而且可以动态修改日志级别,查看方法调用栈,监控方法执行时间,排查问题时非常方便。
OOM后的分析流程
发生OOM后,最关键的是拿到堆转出文件。有两种方式:
- 提前加上JVM参数,让系统自动dump
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/tmp/heapdump.hprof
- 手动用jmap生成
jmap -dump:format=b,file=heap_dump.hprof <pid>
一般生产系统内存在2GB-8GB,dump文件会比较大,这个命令会让应用暂停1-3秒。
拿到dump文件后,用MAT或VisualVM分析。
MAT的Leak Suspects报告会直接列出可以对象,然后看直方图找出占用内存最多的对象类型,再通过右键点击“列出对象(List objects)”->“包含传入引用(with incoming references)”,一层层追溯引用脸,最后定位到具体代码。
常见的OOM原因:
- 缓存没有设置过期时间,对象堆积
- 集合类无限增长,比如List、Map只添加不清理
- 数据库查询一次性加载几十万条数据到内存
- 大对象频繁创建,比如每次请求都new一个几MB的数组
MAT分析完成后,看到引用链上最顶层对象是什么吗,对应到代码里就能找到问题。比如发现某个静态的HashMap里塞了几万个对象,就知道是缓存没清理导致的。
还有一些在线工具比如GCeasy,能分析GC日志,上传日志文件后会生成可视化图表,展示GC频率、停顿时间、内存使用趋势、适合分析GC性能问题。
OOM常见的类型
| OOM错误类型 | 出问题的区域 | 典型触发场景 |
|---|---|---|
| Java heap space | 堆 | 大量对象创建、内存泄漏 |
| StackOverflowError | 栈 | 无限递归、调用链过深 |
| Metaspace/PermGen space | 方法区 | 动态代理生成大量类、频繁热部署 |
| Direct buffer memory | 直接内存 | NIO分配过多ByteBuffer |
| Unable to create new native thread | 操作系统线程资源 | 线程数超出系统限额 |
| GC overhead limit exceeded | 堆 | GC花了98%时间只回收不到2%的内存 |

浙公网安备 33010602011771号