Java 内存泄露排查方法

本文详细解析了Java内存泄漏的概念、原因及表现形式,并介绍了使用jstat命令进行内存泄漏定位的方法,包括各项参数的含义及如何通过数据分析找出内存泄漏问题。

此处先感谢 白灰 的分享

原文地址:java程序内存泄漏的定位与分析

1,什么叫内存泄露

编写java程序最为方便的地方就是我们不需要管理内存的分配和释放,一切由jvm来进行处理,当java对象不再被应用时,等到堆内存不够用时,jvm会进行垃圾回收,清除这些对象占用的堆内存空间,如果对象一直被应用,jvm无法对其进行回收,创建新的对象时,无法从Heap中获取足够的内存分配给对象,这时候就会导致内存溢出。

而出现内存泄露的地方,一般是不断的往容器中存放对象,而容器没有相应的大小限制或清除机制。容易导致内存溢出。

2,内存泄露现象

常常地,程序内存泄漏的最初迹象发生在出错之后,在你的程序中得到一个OutOfMemoryError。这种典型的情况发生在产品环境中,而在那里,你希望内存泄漏尽可能的少,调试的可能性也达到最小。也许你的测试环境和产品的系统环境不尽相同,导致泄露的只会在产品中暴露。这种情况下,你需要一个低负荷的工具来监听和寻找内存泄漏。同时,你还需要把这个工具同你的系统联系起来,而不需要重新启动他或者机械化你的代码。也许更重要的是,当你做分析的时候,你需要能够同工具分离而使得系统不会受到干扰。
  一个OutOfMemoryError常常是内存泄漏的一个标志,有可能应用程序的确用了太多的内存;这个时候,你既不能增加JVM的堆的数量,也不能改变你的程序而使得他减少内存使用。但是,在大多数情况下,一个OutOfMemoryError是内存泄漏的标志。一个解决办法就是继续监听GC的活动,看看随时间的流逝,内存使用量是否会增加,如果有,程序中一定存在内存泄漏。

3,如何发现内存泄露

3.1. jstat -gc pid
   可以显示gc的信息,查看gc的次数,及时间。
   其中最后五项,分别是young gc的次数,young gc的时间,full gc的次数,full gc的时间,gc的总时间。

3.2.jstat -gccapacity pid
   可以显示,VM内存中三代(young,old,perm)对象的使用和占用大小,
   如:PGCMN显示的是最小perm的内存使用量,PGCMX显示的是perm的内存最大使用量,
   PGC是当前新生成的perm内存占用量,PC是但前perm内存占用量。
   其他的可以根据这个类推, OC是old内纯的占用量。

3.3.jstat -gcutil pid
    统计gc信息统计。

3.4.jstat -gcnew pid
    年轻代对象的信息。

3.5.jstat -gcnewcapacity pid
   年轻代对象的信息及其占用量。

3.6.jstat -gcold pid
    old代对象的信息。

3.7.stat -gcoldcapacity pid
   old代对象的信息及其占用量。

3.8.jstat -gcpermcapacity pid
   perm对象的信息及其占用量。

3.9.jstat -class pid
   显示加载class的数量,及所占空间等信息。

3.10.jstat -compiler pid
   显示VM实时编译的数量等信息。

3.11.stat -printcompilation pid
   当前VM执行的信息。

4,一些术语的中文解释:

S0C:年轻代中第一个survivor(幸存区)的容量 (字节)
S1C:年轻代中第二个survivor(幸存区)的容量 (字节)
S0U:年轻代中第一个survivor(幸存区)目前已使用空间 (字节)
S1U:年轻代中第二个survivor(幸存区)目前已使用空间 (字节)
EC:年轻代中Eden(伊甸园)的容量 (字节)
EU:年轻代中Eden(伊甸园)目前已使用空间 (字节)
OC:Old代的容量 (字节)
OU:Old代目前已使用空间 (字节)
PC:Perm(持久代)的容量 (字节)
PU:Perm(持久代)目前已使用空间 (字节)
YGC:从应用程序启动到采样时年轻代中gc次数
YGCT:从应用程序启动到采样时年轻代中gc所用时间(s)
FGC:从应用程序启动到采样时old代(全gc)gc次数
FGCT:从应用程序启动到采样时old代(全gc)gc所用时间(s)
GCT:从应用程序启动到采样时gc用的总时间(s)
NGCMN:年轻代(young)中初始化(最小)的大小 (字节)
NGCMX:年轻代(young)的最大容量 (字节)
NGC:年轻代(young)中当前的容量 (字节)
OGCMN:old代中初始化(最小)的大小 (字节) 
OGCMX:old代的最大容量 (字节)
OGC:old代当前新生成的容量 (字节)
PGCMN:perm代中初始化(最小)的大小 (字节) 
PGCMX:perm代的最大容量 (字节)
PGC:perm代当前新生成的容量 (字节)
S0:年轻代中第一个survivor(幸存区)已使用的占当前容量百分比
S1:年轻代中第二个survivor(幸存区)已使用的占当前容量百分比
E:年轻代中Eden(伊甸园)已使用的占当前容量百分比
O:old代已使用的占当前容量百分比
P:perm代已使用的占当前容量百分比
S0CMX:年轻代中第一个survivor(幸存区)的最大容量 (字节)
S1CMX :年轻代中第二个survivor(幸存区)的最大容量 (字节)
ECMX:年轻代中Eden(伊甸园)的最大容量 (字节)
DSS:当前需要survivor(幸存区)的容量 (字节)(Eden区已满)
TT:持有次数限制
MTT :最大持有次数限制

 

5,如果定位内存泄漏问题常态使用如下命令:

Jstat  -gcutil 【进程ID】 1000 100

1000 代表多久间隔显示一次

100 代表显示一次

例:

S0 S1 E O P YGC YGCT FGC FGCT GCT

0.00 1.46 26.54 4.61 30.14 35 0.872 0 0.000 0.872

0.00 1.46 46.54 4.61 30.14 35 0.872 0 0.000 0.872

0.00 1.46 47.04 4.61 30.14 35 0.872 0 0.000 0.872

0.00 1.46 65.19 4.61 30.14 35 0.872 0 0.000 0.872

0.00 1.46 67.54 4.61 30.14 35 0.872 0 0.000 0.872

0.00 1.46 87.54 4.61 30.14 35 0.872 0 0.000 0.872

0.00 1.46 88.03 4.61 30.14 35 0.872 0 0.000 0.872

1.48 0.00 5.56 4.62 30.14 36 0.874 0 0.000 0.874

 

显示说明:

S0 — Heap上的 Survivor space 0 区已使用空间的百分比
S1 — Heap上的 Survivor space 1 区已使用空间的百分比
E — Heap上的 Eden space 区已使用空间的百分比
O — Heap上的 Old space 区已使用空间的百分比
P — Perm space 区已使用空间的百分比
YGC — 从应用程序启动到采样时发生 Young GC 的次数
YGCT– 从应用程序启动到采样时 Young GC 所用的时间(单位秒)
FGC — 从应用程序启动到采样时发生 Full GC 的次数
FGCT– 从应用程序启动到采样时 Full GC 所用的时间(单位秒)
GCT — 从应用程序启动到采样时用于垃圾回收的总时间(单位秒)

 

<think>嗯,用户这次问的是Java内存泄漏排查方法和工具,结合之前的对话历史,用户似乎对技术实现细节很感兴趣。之前讨论过LightRAG的技术架构,现在转向Java性能调优,说明用户可能是后端开发或运维工程师。 用户提供的引用材料很有价值,特别是[^1][^3]提到的工具链和特征分析。需要重点突出实践性内容,因为用户引用的故障案例都是实战场景。用户特意强调“不是描述案例本身”,说明想要方法论层面的总结。 注意到用户引用的材料存在互补性:[^1]讲基础工具,[^2]讲异常特征,[^3]讲高级分析工具。可以按“现象识别→基础工具→深度分析”的逻辑组织内容。特别要强调MAT的使用技巧,因为引用[^3]显示这是用户关心的重点。 用户可能正在面临实际的内存泄漏问题,所以需要给出可立即操作的步骤。引用[^4]提到的jstat轻量级特性很重要,应该放在初步排查环节。考虑到用户之前关注过性能指标(见LightRAG讨论中的延迟数据),工具的性能开销也需要注明。 需要警惕一个细节:用户引用中出现了三次“传统意义上的内存泄露”,说明需要明确定义。应该在开篇区分Java中严格的内存泄露(GC ROOT未释放)和广义的内存耗尽(如大缓存)。</think>以下是Java内存泄漏排查的完整流程及工具使用指南,结合典型特征和实战工具进行说明: --- ### 一、内存泄漏典型特征 1. **持续增长的堆内存** - 应用运行后堆内存持续上升,即使触发Full GC也无法回落[^1] 2. **频繁Full GC但回收效率低** - GC日志显示`Full GC`次数增加,但`Heap After GC`未显著下降 3. **最终抛出`OutOfMemoryError`** - 错误信息:`java.lang.OutOfMemoryError: Java heap space`[^2] --- ### 二、排查工具链及使用步骤 #### ▶ 阶段1:初步监控(命令行工具) | 工具 | 命令示例 | 关键指标 | |--------|---------------------------|-------------------------| | **jstat** | `jstat -gcutil <pid> 1000` | `OU`(老年代使用率) >90% 且持续增长 | | **jmap** | `jmap -heap <pid>` | 观察各内存区域容量与使用率 | **示例输出分析**: ```bash jstat -gcutil 12345 1000 S0 S1 E O M CCS YGC YGCT FGC FGCT GCT 0.00 99.80 68.45 98.60 95.33 92.15 3420 386.194 15 120.486 506.680 ``` > 当`O`(老年代)持续>98%且`FGC`(Full GC次数)激增时需警惕[^4] --- #### ▶ 阶段2:堆转储分析(MAT/Eclipse) 1. **生成堆转储文件** ```bash jmap -dump:format=b,file=heapdump.hprof <pid> ``` 2. **使用MAT分析** - 打开`heapdump.hprof` → 查看 **Leak Suspects Report** - 关键视图: - `Dominator Tree`:识别内存占用最大的对象树 - `Path to GC Roots`:查看对象引用链[^3] - **泄漏特征**: - 同一类对象大量堆积(如`com.example.User`实例超10万) - 被静态集合(如`static HashMap`)长期引用 **MAT排查示意图**: ```mermaid graph LR A[Leak Suspects Report] --> B[Dominator Tree] B --> C{检查最大对象} C -->|静态集合| D[查看Path to GC Roots] C -->|线程局部变量| E[检查ThreadLocal引用] ``` --- #### ▶ 阶段3:高级诊断(Arthas/JProfiler) 1. **Arthas实时诊断** ```bash # 监控对象创建 monitor -c 5 org.example.Service *Service # 跟踪方法调用栈 trace com.example.LeakyClass process ``` 2. **JProfiler内存快照对比** - 获取两个时间点的堆快照 → 对比对象增长情况 - 定位新增对象类型及分配位置 --- ### 三、常见泄漏场景及解决 | 泄漏类型 | 根本原因 | 解决方案 | |--------------------|---------------------------|------------------------------| | **静态集合泄漏** | `static Map`未移除过期对象| 使用`WeakHashMap`或定期清理 | | **未关闭资源** | 未调用`close()`的I/O流 | try-with-resources语法 | | **ThreadLocal滥用** | 线程复用未清理ThreadLocal | 使用后执行`remove()` | | **监听器未注销** | 事件监听器未反注册 | 生命周期结束时调用`removeListener()` | --- ### 四、最佳实践 1. **编码阶段预防** - 使用`@LeakCanary`检测Android应用 - 静态代码分析工具:SpotBugs检查`RI_REDUNDANT_INTERFACES` 2. **压测阶段监控** - JMeter压力测试中集成Java Mission Control监控堆 3. **生产环境保障** ```bash # JVM启动参数添加OOM自动转储 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp ``` --- > **技术要点**:内存泄漏本质是**GC Roots可达但永不使用的对象**。通过`jmap`+`MAT`可精确定位到持有这些对象的根引用链[^3]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值