怎么分析JVM当前的内存占用情况?OOM后怎么分析?

重点

首先需要理解JVM的内存组成,参考:JVM的内存区域是如何划分的?

分析JVM内存可以用jstat和jmap这两个命令:

  • jstat能实时监控堆内存、GC情况,适合看活跃状态
  • jmap能生成堆快照,拿到详细的对象分布信息

实时监控流程:

  1. jstat -gc <pid> 1000 10 每1秒打印一次内存使用情况,连续观察10次
  2. 重点关注Eden、Old区的使用率,以及FGC的频率
  3. 如果FGC频率很高,说明老年代快撑不住了

OOM后定位流程:

  1. 用jmap生成堆转储文件,或者提前加上JVM参数-XX:+HeapDumpOnOutOfMemoryError让系统自动dump
  2. 把dump文件导入MAT或VisualVM这些工具
  3. 找出占用内存最多的对象,追溯它的引用链
  4. 定位到具体代码,看是缓存没清理还是集合无限增长
// 添加 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后,最关键的是拿到堆转出文件。有两种方式:

  1. 提前加上JVM参数,让系统自动dump
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/tmp/heapdump.hprof
  1. 手动用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原因:

  1. 缓存没有设置过期时间,对象堆积
  2. 集合类无限增长,比如List、Map只添加不清理
  3. 数据库查询一次性加载几十万条数据到内存
  4. 大对象频繁创建,比如每次请求都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%的内存
posted @ 2026-04-16 14:46  生活的样子就该是那样  阅读(26)  评论(0)    收藏  举报