Linux指令实战学习之内存泄漏
1. 如何快速定位内存泄漏?
定位内存泄漏一般可以分三步走。
1.1 确认是否真的泄露
最简单的方式是通过如下指令确认:
jstat -gcutil pid 1000接下来让我分析一下指令含义:
首先是jstat,它是JDK自带工具,不在系统自带命令集,必须满足两点:
a. 服务器/本机安装了JDK(不是只装JRE),配置好环境变量
b. 命令执行终端和Java进程在同一操作系统的用户下(root进程普通用户看不到,会报找不到进程)
然后就是配套参数:
gcutil:打印GC统计百分比(使用率、回收次数、耗时),最常用的GC监控参数;pid:目标Java应用进程 ID;1000:采样间隔,单位毫秒,每 1000ms(1 秒)输出一行数据;- 无末尾数字 = 只输出1次,加数字 = 持续滚动输出。
直观效果:
终端会每秒刷新一行GC指标,持续打印,直到手动按Ctrl+C终止命令。 用于实时观察堆内存占用、GC 频率、STW 耗时,排查内存泄漏、频繁 FullGC、OOM 问题。
标准输出样例:
S0 S1 E O M CCS YGC YGCT FGC FGCT GCT 0.00 22.35 45.21 18.64 92.10 88.32 123 1.456 2 0.872 2.328字段解释:
| 字段 | 含义 |
|---|---|
| S0 | Survivor0 区内存使用率 % |
| S1 | Survivor1 区内存使用率 % |
| E | Eden 新生代使用率 % |
| O | Old 老年代使用率 % |
| M | Metaspace 元空间使用率 % |
| CCS | 压缩类空间使用率 |
| YGC | 年轻代 GC(Minor GC)总次数 |
| YGCT | 所有 Minor GC 总耗时(秒) |
| FGC | Full GC 总次数 |
| FGCT | 所有 Full GC 总耗时(秒) |
| GCT | 程序启动以来 GC 总耗时 |
观察GC情况:
如果老年代内存使用率一直涨,执行Full GC后也降不下来,那基本就是有内存泄漏了。或者看监控曲线,内存一直往上走,呈现锯齿状但低点越来越高,这就是典型特征。
1.2 通过dump堆快照导出对象
指令如下:
jmap -dump:live,format=b,file=heap.hprof pid接下来让我分析一下指令含义:
首先是jmap,它是JDK自带堆快照工具,用于导出Java进程堆内存文件,以便离线分析内存泄漏、大对象、OOM。
它的使用条件同jstat一致。
-dump:
代表执行堆转储(dump堆快照),后面跟多个配置参数,用逗号分隔。
参数逐项解析:
live:
只导出当前存活对象,它会先触发一次FullGC,把已经没有引用的垃圾对象过滤掉,快照文件更小、分析更干净。
如果去掉live,就会导出全量堆,包含已死亡未回收对象,文件更大。
format=b:
固定写法,b= binary二进制格式,标准hprof二进制文件,是MAT、JProfiler、IDEA内存分析工具通用格式,不可省略。
file=heap.hprof:
指定导出的文件名heap.hprof,文件生成在当前执行命令的目录,可自定义路径,例如file=/tmp/heap_20260704.hprof。
pid:
目标 Java 进程号,可以用“ jps -l ”或者“ ps -ef | grep java ”获取。
执行后效果:
a. 程序会暂停STW(Stop-The-World),堆越大停顿越久,线上业务高峰期慎用;
b. 磁盘生成.hprof二进制堆文件;
c. 导出完成后终端输出完成提示,Java进程恢复正常运行。
如果担心OOM突然发生来不及dump,可以提前加JVM参数-XX:+HeapDumpOnOutOfMemoryError,这样OOM时会自动dump。
1.3 用MAT分析(也可选择其他内存分析工具)
用Eclipse Memory Analyzer,打开.hprof文件后它会自动给一个Leak Suspects报告,通常能直接定位问题。
如果报告不够明确,可以看Histogram视图,找到实例数量特别多或者占用内存特别大的对象,然后右键选择Path to GC Roots,选exclude weak references,这样就能看到是哪些强引用持有了这个对象导致它回收不了。
比如说你可能看到某个静态HashMap持有了这些对象,或者被ThreadLocal持有,问题就很清楚了。
1.4 总结
还有个实用技巧是对比两次堆快照。
间隔一段时间dump两次,用MAT的Compare功能对比,增长最多的对象往往就是泄漏的对象,这对缓慢泄漏的问题特别有效。
如果是本地环境调试,可以直接用JProfiler实时监控,能看到内存实时分配情况和对象引用关系,比dump快照更直观。
基本上这套流程下来,大部分内存泄漏都能定位到。
关键就是先确认有泄漏,然后dump堆快照,最后用MAT分析GC Roots引用链,找到是哪里的代码持有了不该持有的引用。
大多数遇到的内存泄漏问题,十有八九都是静态集合没清理、ThreadLocal没remove或者监听器没反注册这几种情况,看到引用链基本就能马上判断出来。