挑个趁手的好工具
mac中安装MAT,不需要先装Eclipse,可以直接安装MAT。
下载
https://www.eclipse.org/mat/downloads.php
启动
下载完成后解压,到目录mat.app/Contents/MacOS
下启动 ./MemoryAnalyzer -data ./workspace
。注意,初始分配的堆大小可能不足,需要去 mat.app/Contents/Eclipse/MemoryAnalyzer.ini
修改-Xmx4
。
实战
代码 & 运行
用一个在极客时间的例子,在ThreadLocal中分配4Mb的Byte:
@RequestMapping(value = "/test0")
public String test0(HttpServletRequest request) {
ThreadLocal<Byte[]> localVariable = new ThreadLocal<Byte[]>();
localVariable.set(new Byte[4096*1024]);// 为线程添加变量
return "success";
}
git: https://github.com/yizhanggou/demo
命令:java -jar -Xms256m -Xmx512m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=<your_folder>/heapdump.hprof -XX:+PrintGCTimeStamps -XX:+PrintGCDetails -Xloggc:<your_folder>/heapTest.log demo-0.0.1-SNAPSHOT.jar
实战
测试
请求10000次:
ab -n 10000 http://localhost:8080/test0
因为堆开的太小所以很快就爆OOM了:
linux命令初筛
接下来看一下内存占用多少:
宿主机是大概3G,即占用了四百多M
再看线程占用情况:
看nio的线程的jstack:
-
获取十六进制
-
查看线程状态,可以看到一直处于WAITING
再看jamp占用情况:可以看到老年代几乎占满了。
初步判断发生了内存泄漏,那么我们来看一下内存分配情况:占第一排的是Byte
说明代码中可能存在Bytes内存泄漏,我们把启动时指定的dump文件下载下来看。(PS:压缩一下会节省很多空间)
MAT中查看哪里泄漏
MAT中打开
可以看到已经提示有内存泄漏异常:
点击进去Class Histogram
查看具体哪个对象引用:
可以看到是ThreadLocal引用了:
纠正
添加remove
添加remove后,就不会爆内存泄漏了:
@RequestMapping(value = "/test0")
public String test0(HttpServletRequest request) {
ThreadLocal<Byte[]> localVariable = new ThreadLocal<Byte[]>();
localVariable.set(new Byte[4096*1024]);// 为线程添加变量
localVariable.remove();
return "success";
}
去掉ThreadLocal
改为本地变量也不会爆内存泄漏:
@RequestMapping(value = "/test0")
public String test0(HttpServletRequest request) {
Byte[] bytes = new Byte[4096*1024];
return "success";
}
下一篇会讲为什么ThreadLocal会爆内存泄漏。