内存泄漏排查

问题定位

大量报错报警,查看日志,发现如下堆栈:

java.io.BufferedReader.readLine(BufferedReader.java:371)
java.io.BufferedReader.readLine(BufferReader.java:389)
java_io_BufferedReader$readLine.call(Unknown Source)
com.domain.detect.http.HttpClient.getResponse(HttpClient.groovy:122)
com.domain.detect.http.HttpClient.this$2$getResponse(HttpClient.groovy)

没有头绪,于是在一次报警时,快速登录服务器,使用top、free、df三板斧,结果发现了些异常:

image.png

发现CPU占用率特别高,知道这个时间点并没有做大量CPU运算,于是怀疑是死循环,或大量GC导致的。
于是使用 jstat -gc pid [interval] 查看java进程的gc状态, 发现Full GC频率很高
image.png

于是想到可能是内存泄漏了。
使用jstack pid > jstack.log 保存线程栈的现场。
使用```jmap -dump:format=file,file=heap.log pid 保存堆现场

然后重启服务,报警停止

排查

为了防止问题再次发生,需要定位到根源

分析栈

栈的分析很简单,看下线程数是不是过多,多数栈都在干什么

> grep 'java.lang.Thread.State' jstack.log  | wc -l
> 464

发现只有400多线程,并无异常

> grep -A 1 'java.lang.Thread.State' jstack.log  | grep -v 'java.lang.Thread.State' | sort | uniq -c |sort -n

     10  at java.lang.Class.forName0(Native Method)
     10  at java.lang.Object.wait(Native Method)
     16  at java.lang.ClassLoader.loadClass(ClassLoader.java:404)
     44  at sun.nio.ch.EPollArrayWrapper.epollWait(Native Method)
    344  at sun.misc.Unsafe.park(Native Method)

线程状态也无异常

分析堆文件

下载dump文件到本地(可以先压缩),使用mat工具分析
MAT 是分析 Java 堆内存的利器,使用它打开我们的堆文件(将文件后缀改为 .hprof), 它会提示我们要分析的种类,对于这次分析,果断选择 memory leak suspect。


image.png

从上面的饼图中可以看出,绝大多数堆内存都被同一个内存占用了,再查看堆内存详情,向上层追溯,很快就发现了罪魁祸首。


image.png
分析代码

找到内存泄漏的对象了,在项目里全局搜索对象名,它是一个 Bean 对象,然后定位到它的一个类型为 Map 的属性。
这个 Map 根据类型用 ArrayList 存储了每次探测接口响应的结果,每次探测完都塞到 ArrayList 里去分析,由于 Bean 对象不会被回收,这个属性又没有清除逻辑,所以在服务十来天没有上线重启的情况下,这个 Map 越来越大,直至将内存占满。
内存满了之后,无法再给 HTTP 响应结果分配内存了,所以一直卡在 readLine 那。而我们那个大量 I/O 的接口报警次数特别多,估计跟响应太大需要更多内存有关。

你可能感兴趣的:(内存泄漏排查)