文章目錄
- Spring Boot —— 如何排查內存溢出問題
- 前言
- 場景一
- 思考
- 常用排查命令
- jstat -class PID
- jstat -compiler PID
- jstat -gc PID
- jstat -gccapacity PID
- jstat -gcutil PID
- jstat -gccause PID
- jstat -gcnew PID
- jstat -gcnewcapacity PID
- jstat -gcold PID
- jstat -gcoldcapcacity PID
- jstat -printcompilation PID
- jmap -histo PID
- jmap -heap PID
- jmap -dump:live,format=b,file=/tmp/m.hprof PID
- jhat -J-Xmx2048m -port 5000 /tmp/m.hprof
- jcmd PID GC.run
- 如何排查?
- 如何解決?
- 最終解決
Spring Boot —— 如何排查內存溢出問題
前言
總結看到的博客,記錄下,有備無患。
場景一
Spring Boot 項目JVM啓動配置爲 -Xms4g -Xmx4g,程序運行一段時間內存越來越高,最終超過了可承受的內存容量,只能臨時重啓服務,達到應用有效。
思考
常用排查命令
jstat -class PID
jstat -compiler PID
jstat -gc PID
jstat -gccapacity PID
jstat -gcutil PID
查看堆比例
jstat -gccause PID
jstat -gcnew PID
jstat -gcnewcapacity PID
jstat -gcold PID
jstat -gcoldcapcacity PID
jstat -printcompilation PID
jmap -histo PID
查看類的實例
jmap -heap PID
查看堆棧信息
jmap -dump:live,format=b,file=/tmp/m.hprof PID
保存內存的堆棧爲文件
jhat -J-Xmx2048m -port 5000 /tmp/m.hprof
在線查看堆文件的類,速度比較慢
jcmd PID GC.run
強制gc
如何排查?
1.開發環境和測試環境調試
使用jdk自帶的jvisualvm.exe,查看佔空間的類和實例最多的類,找到其最近的內存釋放點一般就是內存泄漏的對象。
也可以使用jmap查看jvm進程實例最多的類。
- 本機啓動程序,postman或jmeter調用接口,可以直接用jvisualvm查看堆棧信息
- 遠程調試,在測試環境啓用jmxremot,如下:
Dcom.sun.management.jmxremote.port=10096 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false
但是堆棧信息只能先保存在測試服務器上,下載到本地後再用jvisualvm.exe打開
2.保存生產環境的內存進行分析
先使用jmap命令將程序的內存數據保存下來,jmap -dump:live,format=b,file=m.hporf PID,
其中PID是程序的進程號:
/data/server/jdk/jdk1.8.0_171/bin/jmap -dump:live,format=b,file=/tmp/m.hprof 3478
將/tmp/m.hprof堆棧文件下載到本地電腦,jvisualvm.exe打開分析
用mat分析,在空閒同網段生產服務器使用,阿里雲內網傳輸文件很快,將下載的mat直接解壓縮就可以使用了,文件如果太大需修改 MemoryAnalyzer.ini中的參數爲-Xmx4096M。
cd /data/tools/mat
./ParseHeapDump.sh
/tmp/m.hprof
org.eclipse.mat.api:suspects
org.eclipse.mat.api:overview
org.eclipse.mat.api:top_components
程序執行完,會在/tmp下生成一些文件,其中m_System_Overview.zip ,m_Top_Components.zip,m_Leak_Suspects.zip三個壓縮文件就是報告,可以看到程序的運行概況,最大的對象,和推測泄露點。
從我的內存泄露報告中可以看到Global對象groovy.lang.MetaClassImpl對象是兩個主要的內存泄露點。
如何解決?
發現問題了,該如何解決。
方法1
覆盤程序問題,將groovy腳本封裝在ThreadLocal中,在一次請求中可以被重複利用,該ThreadLocal包括了Global對象,在函數處理 **結束後釋放掉ThreadLocal
**對象,主要內存泄漏點即可修復。
方法2
groovy.lang.MetaClassImpl泄露解決,先升級groovy-all的版本,發現很多實例其實已經不再引用,但是內存卻無法釋放,當系統內存不足的時候纔會被釋放掉。
最終解決
spring-boot 升級到2.1.1.RELEASE
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.1.RELEASE</version>
<type>pom</type>
</dependency>
廢用tomcat容器,改用undertow當做容器
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>
java啓動參數的順序調整
生產環境的spring boot工程-Xmx出現有的生效,有的無效,並且所有的jar包jvm參數全放到jar包後面。調整一下順序,如下:
java -jar -Xmx2048m -Xms1024m test.jar --spring.profiles.active=prod
注:本地電腦調整順序無效,在生產有效。