在 Java 中分析 JVM 内存占用情况以及处理 OOM(Out - Of - Memory)问题,可从以下多个方面进行,包含多种工具和方法的使用:
一、分析 JVM 当前内存占用情况
(一)命令行工具
- jps与jstat
- jps:用于查看当前运行的 Java 进程 ID。例如,在命令行输入
jps
,会列出所有 Java 进程及其对应的主类或 JAR 包信息,格式如下:
- jps:用于查看当前运行的 Java 进程 ID。例如,在命令行输入
1234 MyApp
5678 org.apache.catalina.startup.Bootstrap
这里的 1234
、5678
就是进程 ID(PID)。
- jstat:借助 jps 获取到的 PID,可使用 jstat -gc [PID] [间隔时间] [次数]
命令查看 JVM 垃圾回收相关的统计信息,从而了解内存占用情况。比如 jstat -gc 1234 1000 5
,表示每隔 1000 毫秒(1 秒)查询一次 PID 为 1234 的 Java 进程的 GC 情况,共查询 5 次。输出结果类似:
S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT
1024.0 1024.0 0.0 512.0 8192.0 4096.0 16384.0 8192.0 4096.0 3072.0 512.0 400.0 10 0.500 2 1.000 1.500
各列含义:
- S0C
、S1C
: survivor 0、1 区的容量(单位:KB)。
- S0U
、S1U
: survivor 0、1 区的已用空间。
- EC
、EU
: Eden 区容量和已用空间。
- OC
、OU
:老年代容量和已用空间。
- MC
、MU
:元空间(Metaspace)容量和已用空间。
- YGC
、YGCT
:年轻代垃圾回收次数和耗时。
- FGC
、FGCT
:Full GC 次数和耗时。
- GCT
:总的垃圾回收耗时。
2. jmap
可以使用 jmap -heap [PID]
命令查看 JVM 堆内存的详细配置和使用情况,包括堆的各个分代(年轻代、老年代等)的大小、使用比例等信息 。例如:
Attaching to process ID 1234, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 11.0.12
using thread-local object allocation.
Garbage-First (G1) GC with 4 thread(s)
Heap Configuration:
MinHeapFreeRatio = 40
MaxHeapFreeRatio = 70
MaxHeapSize = 8589934592 (8192.0MB)
NewSize = 13631488 (13.0MB)
MaxNewSize = 4294967296 (4096.0MB)
OldSize = 54525952 (52.0MB)
NewRatio = 2
SurvivorRatio = 8
MetaspaceSize = 21807104 (20.796875MB)
CompressedClassSpaceSize = 1073741824 (1024.0MB)
MaxMetaspaceSize = 17592186044415 MB
G1HeapRegionSize = 4194304 (4.0MB)
Heap Usage:
G1 Heap:
regions = 2048
capacity = 8589934592 (8192.0MB)
used = 3221225472 (3072.0MB)
free = 5368709120 (5120.0MB)
37.5% used
G1 Young Generation:
Eden Space:
regions = 768
capacity = 3221225472 (3072.0MB)
used = 3221225472 (3072.0MB)
free = 0 (0.0MB)
100.0% used
Survivor Space:
regions = 0
capacity = 0 (0.0MB)
used = 0 (0.0MB)
free = 0 (0.0MB)
0% used
G1 Old Generation:
regions = 0
capacity = 5368709120 (5120.0MB)
used = 0 (0.0MB)
free = 5368709120 (5120.0MB)
0% used
还能使用 jmap -histo:live [PID]
查看堆中存活对象的统计信息,包括类名、实例数量、占用内存大小等,示例输出:
num #instances #bytes class name
----------------------------------------------
1: 1000000 40000000 com.example.MyObject
2: 10000 2000000 java.lang.String
3: 5000 1000000 java.util.HashMap$Node
...
(二)可视化工具
- JConsole
JDK 自带的图形化监控工具,在命令行输入jconsole
启动,然后选择要连接的 Java 进程。它可以直观地查看堆内存、线程、类加载等信息,在“内存”标签页中,能看到堆内存和非堆内存(如元空间)的使用趋势、当前大小、已用大小等详细数据,还可进行垃圾回收操作并观察内存变化。 - VisualVM
功能强大的多合一故障诊断和性能监控工具。启动后添加要监控的 Java 进程,在“概述”标签可看到基本的内存、CPU、线程等信息;“监视”标签能详细查看堆内存、元空间等的使用情况,支持生成堆转储(Heap Dump)文件;“ Profiler” 标签可进行 CPU 和内存的剖析等。它还可以安装各种插件来扩展功能,比如用于分析内存泄漏的插件等。
二、OOM 后分析
当发生 OOM(java.lang.OutOfMemoryError
)后,主要通过分析堆转储(Heap Dump)文件来排查问题,以下是具体步骤和方法:
(一)生成堆转储文件
- 自动生成
可以在 JVM 启动参数中配置,当发生 OOM 时自动生成堆转储文件,添加参数:
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/path/to/dumpfile.hprof
这样,当 OOM 发生时,JVM 会在指定的 /path/to/dumpfile.hprof
路径下生成堆转储文件。
2. 手动生成
在 OOM 发生后但进程还未退出时,可使用 jmap -dump:live,format=b,file=/path/to/dumpfile.hprof [PID]
命令手动生成堆转储文件,不过这种情况要确保进程还能响应命令执行,若进程已崩溃可能无法执行。
(二)分析堆转储文件
- 使用 Eclipse Memory Analyzer(MAT)
- 这是一款专门用于分析 Java 堆转储文件的强大工具。将生成的
.hprof
文件导入 MAT 后,它会提供多种分析功能:- 概览:展示堆的总体信息,如总内存大小、占用内存最多的对象类型等。
- Dominator Tree(支配树):查看哪些对象占用了大量内存,以及对象之间的引用关系,能快速定位大对象和内存泄漏点。例如,某个大的集合对象(如
ArrayList
、HashMap
等)持有大量其他对象的引用,导致无法被垃圾回收。 - Leak Suspects(内存泄漏怀疑点):MAT 会自动分析并列出可能存在内存泄漏的嫌疑对象及相关信息,辅助开发人员快速定位问题根源。
- Histogram(直方图):按类名统计对象的数量和占用内存大小,可对比不同类的内存占用情况,找出异常占用内存的类。
- 这是一款专门用于分析 Java 堆转储文件的强大工具。将生成的
- 使用 VisualVM 分析
VisualVM 也支持打开堆转储文件进行分析,在 VisualVM 中导入.hprof
文件后,同样可以查看对象的分布、引用关系等信息,进行类似 MAT 的一些基本分析操作,不过在复杂内存泄漏分析方面,功能可能稍弱于 MAT,但对于一些简单的 OOM 问题排查也很有帮助。
(三)结合代码分析
通过工具找到占用内存大的对象或疑似内存泄漏的对象后,要结合对应的 Java 代码进行分析,查看这些对象的创建逻辑、生命周期管理等,判断是否存在对象创建过多、长期无法释放(如静态集合类持有大量对象引用未清理等)等导致 OOM 的原因 。例如,如果发现某 HashMap
对象占用内存极大且无法被回收,就要查看代码中该 HashMap
的使用场景,是否存在不断向其中添加元素但没有相应删除操作的情况,或者是否存在不合理的对象引用持有导致内存无法释放。
总体而言,分析 JVM 内存占用是一个日常监控和优化的过程,而 OOM 后的分析则更侧重于通过堆转储文件和代码结合,找出内存溢出的根本原因并加以修复。