一、排查应用内存使用过大问题
本文使用JVM
常用的检查工具 jcmd、jmap 、mat
工具排查内存使用过大问题。
构建问题环境
在开始前我们先准备测试的应用,这里可以新建一个SpringBoot
项目,在项目中我们故意写一个占用内存的BUG
:
@RestController
public class JvmThreadController {
List<byte[]> memoryList = new ArrayList<>();
@GetMapping("/memoryTest")
public String memoryTest(int c) {
byte[] b = new byte[c * 1024 * 1024];
memoryList.add(b);
return "success";
}
}
上面我们创建了一个controller
,声明了一个byte[]
类型的 memoryList
列表,并在memoryTest
接口中对memoryList
每次以 c
兆的大小进行塞值,由于memoryList
是全局变量,并不会被GC
回收所以,以此我们就可以达到内存占用量大的目的了。
在启动时,我们最好也指定下JVM
的参数,将最大堆内容设为50M
,这样就可以很快出现OOM
的问题了,并且我们也可以打印出内存溢出时的hprof
文件:
java -Xmx50m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/java/ -jar jvm-demo-0.0.1-SNAPSHOT
下面多调用几次memoryTest
接口:
已经出现问题,下面我们开始排除问题
排查问题
使用 jcmd
查看目标JVM的进程:
jcmd
可以看到进程ID
为 11420
。
下面使用 jmap -heap
查看堆内存使用情况
jmap -heap 11420
可以看出 eden
区和 老年代使用率都接近满的状态了,如果我们应用没有大的对象,那就是肯定某个对象站用了大量的空间。
下面使用 jmap -histo
查看对象的分布情况:
jmap -histo 11420
内存占用第一的是 [B
,表示字节数组,因此内存使用过大问题有可能可字节数组有关。
下面使用 jmap
导出进程的内存映像文件:
jmap -dump:file=headdump.hprof 11420
下面可以使用 jhat
解析上面生成的文件,这里为了方便查看使用 Mat
工具打开,如果没有安装可去下面链接下载:
https://eclipse.dev/mat/previousReleases.php
版本太高的对 JDK
的版本也要求高,如果是 JDK 8
可以下载 1.10.0
的版本,下载好后解压运行 MemoryAnalyzer.exe
。
使用 Mat
工具打开上面生成的内存映像文件,并查看 Leak Suspects
:
给出了一个问题:
查看这个问题的详情:
看到这个地方比较明确了问题所在,在 com.example.jvmdemo.controller.JvmThreadController
类下,并且和byte[]
有关,还和 java.util.ArrayList
有关,查看这个地方的代码:
找到这些特征,很明显看到问题所在。