一、 准备测试应用
- 新建一个 SpringBoot应用,写一段有 OOM bug 的代码:
@RestController
@RequestMapping
public class JvmThreadController {List<TestWrapper> memoryList = new ArrayList<>();@GetMapping("/test")public String memoryTest(@RequestParam("count") Integer count) {for (int i = 0; i < count; i++) {byte[] b = new byte[1024];memoryList.add(new TestWrapper(b));}return "success";}static class TestWrapper{byte[] b;public TestWrapper(byte[] b) {this.b = b;}}
}
-
调整JVM参数,可以快速 OOM
-Xmx50m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/java/
-
调用memoryTest接口,项目 OOM
二、使用Arthas排查问题
-
启动 Arthas:
java -jar arthas-boot.jar
-
查看内存使用情况
dashboard
-
使用Arthas生成火焰图
-
开始生成:
profiler start --event alloc
-
查看采集的sample的数量:
profiler getSamples
-
停止采集,并生成html文件,可以指定文件存放地址:`profiler stop --format html --file /java/output.html
- 分析火焰图
根据火焰图可以看出问题,是com/arthas/demo/controller/JvmThreadController.memoryTest
方法有问题,并且和byte[]
有关
三、另一种排查方式
-
使用
jps
命令查看项目进程
-
使用
jmap -histo:live 37446 | grep demo
命令查询堆内存中的对象分配统计信息,通过jmap命令配合-histo:live选项获取的,它展示了指定Java进程(PID为37446)的存活对象直方图。
命令解释:
- jmap: 是一个JDK自带的命令行工具,用于打印堆内存的详细信息,包括堆内存使用情况、dump堆内存到文件等。
- histo:live: 参数指定只统计活动对象(即未被垃圾回收器标记为可回收的对象)的信息。
- 37446:PID
- grep:执行搜索命令。
- demo:是要搜索的模式或字符串。在这里,我希望查找那些输出行中包含 “demo” 字符串的行。
输出解读:
- 第一列:19, 这是一个索引编号,表示列表中的第19个条目。
- 第二列:4200, 实例数量,即堆中有4200个com.arthas.demo.controller.JvmThreadController$TestWrapper类型的对象。
- 第三列:67200, 占用内存大小,单位是字节,表明这4200个TestWrapper实例总共占用了67,200字节的堆空间。
jmap -histo:live 37446 | grep demo
命令默认是按照实例数量倒序排序的,但是有可能会出现大量数据,所以我们可以把命令优化成按照实例数量倒序排序,并且只取前 15 条数据:
jmap -histo:live 7 | awk '/demo/ {print $1, $2, $3, $4}' | sort -k2,2rn | head -n 15