說明
一般我們發現內存持續增長,但是並沒有得到釋放,我們就需要排查是否內存泄露
代碼模擬
通過ThreadLocal模擬內存泄露
為什么ThreadLocal會內存泄露?參考:《ThreadLocal》
@RequestMapping("/testController") @Controller public class TestController { @RequestMapping(value = "/test3") @ResponseBody public String test3() { ThreadLocal localVariable = new ThreadLocal(); localVariable.set(new Byte[4096 * 1024]);// 為線程添加變量 return "success"; } }
AB壓測模擬
ab使用例子:《壓測工具-ab》
ab -c 500 -n 1000 localhost:8999/testController/test3
分析
1.當我們發現機器內存持續升高 我們可以使用top命令來定位java程序
2.我們可以通過top -Hp 查看各個線程cpu 和 內存占用情況
紅框框起來的內存是我有個疑惑,應該是各個線程占用的內存,但是這里好像顯示的是總內存;可能是因為堆內存是線程共享 所以這里顯示的是總的堆占用內存。
3.我們可以通過jstack dump到線程堆棧,對於分析線程死鎖 cpu占用過高代碼定位很有幫助
這里我們以21141為例,先將21141轉換為16進制:在線轉換 得到5295 通過jstack dump線程堆棧信息
或者通過命令 printf '%x/n' tid 把線程id轉化為十六進制
jstack 19751|grep -A20 5295 //查找5295 並打印后20行 19751為PID 5295 為線程id 16進制 jstack 19751|grep -A20 5295 >/root/threaddump.txt//查找5295 並打印后20行 並輸出到指定文件 jstack 19751 >/root/threaddump.txt// dump整個線程堆棧並輸出到指定文件
可以發現該線程一直處於 TIMED_WAITING 狀態 此時 CPU 使用率和負載並沒有出現異常,我們可以排除死鎖或 I/O 阻塞的異常問題了。
4.通過jmap或者jstack查看堆內存使用情況
參數詳解請看:《jvm-內存監控工具》
可以發現,老年代的使用率幾乎快占滿了,而且內存一直得不到釋放
./jmap -heap 19751
./jstack -gc 19751
5.查看gc信息
參數詳解請看:《jvm-內存監控工具》
jstat -gcutil pid 1000//1秒一次采集gc信息
6.我們可以通過jmap查看內存存活對象情況
可以發現[Ljava.lang.Byte; 有161個實例 占用了大量空間
jmap -histo pid 所有對象創建數量 jmap -histo:live pid 只查看存活對象 jmap -histo pid >/root/object.txt 所有對象創建數量並保存到指定文件 jmap -histo:live pid /root/object.txt 只查看存活對象並保存到指定文件
7.dump內存信息進行分析參考:《JVM學習-內存監控工具(五)-dump內存信息》
jmap -dump:format=b,file=/Users/liqiang/Desktop/logs/heap.hprof pid ///Users/liqiang/Desktop/logs/heap.hprof為輸出文件
分析dump *.hprof文件工具
visualVM
下載地址:https://visualvm.github.io/download.html
1導入dump文件
這里可以分析線程和內存對象情況,我們切換到Object
我們可以看到Byte占用了90%以上的內存 我們可以查看引用對象
看到引用對象是ThreadLocal 所以判定是使用ThreadLocal 使用完畢並沒有釋放導致內存泄露
MAT
下載地址需要FQ 暫時不測試了
異常情況解決方式
1.Unable to open socket file: target process not responding or HotSpot VM not loaded The -F option can be used when the target process is not responding
切換到程序所在的用戶
2.注:如果使用jmap和jstack報錯 可能是多jdk導致切換當前使用jdk 在bin下面通過./指定調用:參考《linux查看jdk安裝路徑》