一次恐怖的 Java 内存泄漏排查实战
轉(zhuǎn)載自??一次恐怖的 Java 內(nèi)存泄漏排查實戰(zhàn)
最近在看《深入理解Java虛擬機(jī):JVM高級特性與最佳實踐》(第二版)這本書,理論+實踐結(jié)合,深入淺出,強(qiáng)烈推薦給大家。
這兩天對JVM內(nèi)容進(jìn)行了一個討論,討論的內(nèi)容主要包括如下幾個方面。
1)內(nèi)存溢出和內(nèi)存泄露的介紹?
2)如何排查和處理內(nèi)存泄露?
?
一、內(nèi)存溢出和內(nèi)存泄露
一種通俗的說法。
1、內(nèi)存溢出:你申請了10個字節(jié)的空間,但是你在這個空間寫入11或以上字節(jié)的數(shù)據(jù),出現(xiàn)溢出。
2、內(nèi)存泄漏:你用new申請了一塊內(nèi)存,后來很長時間都不再使用了(按理應(yīng)該釋放),但是因為一直被某個或某些實例所持有導(dǎo)致 GC 不能回收,也就是該被釋放的對象沒有釋放。點擊此處查看內(nèi)存泄漏更多說明。
?
下面具體介紹。
1.1 內(nèi)存溢出
java.lang.OutOfMemoryError,是指程序在申請內(nèi)存時,沒有足夠的內(nèi)存空間供其使用,出現(xiàn)OutOfMemoryError。點擊此處查看內(nèi)存泄漏更多說明。
產(chǎn)生原因
產(chǎn)生該錯誤的原因主要包括:
-
JVM內(nèi)存過小。
-
程序不嚴(yán)密,產(chǎn)生了過多的垃圾。
?
程序體現(xiàn)
一般情況下,在程序上的體現(xiàn)為
-
內(nèi)存中加載的數(shù)據(jù)量過于龐大,如一次從數(shù)據(jù)庫取出過多數(shù)據(jù)。
-
集合類中有對對象的引用,使用完后未清空,使得JVM不能回收。
-
代碼中存在死循環(huán)或循環(huán)產(chǎn)生過多重復(fù)的對象實體。
-
使用的第三方軟件中的BUG。
-
啟動參數(shù)內(nèi)存值設(shè)定的過小。
?
錯誤提示
此錯誤常見的錯誤提示:
?
解決方法
1)增加JVM的內(nèi)存大小
對于tomcat容器,找到tomcat在電腦中的安裝目錄,進(jìn)入這個目錄,然后進(jìn)入bin目錄中,在window環(huán)境下找到bin目錄中的catalina.bat,在linux環(huán)境下找到catalina.sh。
編輯catalina.bat文件,找到JAVA_OPTS(具體來說是?set "JAVA_OPTS=%JAVA_OPTS% %LOGGING_MANAGER%")這個選項的位置,這個參數(shù)是Java啟動的時候,需要的啟動參數(shù)。
也可以在操作系統(tǒng)的環(huán)境變量中對JAVA_OPTS進(jìn)行設(shè)置,因為tomcat在啟動的時候,也會讀取操作系統(tǒng)中的環(huán)境變量的值,進(jìn)行加載。
如果是修改了操作系統(tǒng)的環(huán)境變量,需要重啟機(jī)器,再重啟tomcat,如果修改的是tomcat配置文件,需要將配置文件保存,然后重啟tomcat,設(shè)置就能生效了。
2)優(yōu)化程序,釋放垃圾
主要思路就是避免程序體現(xiàn)上出現(xiàn)的情況。避免死循環(huán),防止一次載入太多的數(shù)據(jù),提高程序健壯型及時釋放。因此,從根本上解決Java內(nèi)存溢出的唯一方法就是修改程序,及時地釋放沒用的對象,釋放內(nèi)存空間。
?
1.2?內(nèi)存泄露
Memory Leak,是指程序在申請內(nèi)存后,無法釋放已申請的內(nèi)存空間,一次內(nèi)存泄露危害可以忽略,但內(nèi)存泄露堆積后果很嚴(yán)重,無論多少內(nèi)存,遲早會被占光。
在Java中,內(nèi)存泄漏就是存在一些被分配的對象,這些對象有下面兩個特點。
1)首先,這些對象是可達(dá)的,即在有向圖中,存在通路可以與其相連;
2)其次,這些對象是無用的,即程序以后不會再使用這些對象。
如果對象滿足這兩個條件,這些對象就可以判定為Java中的內(nèi)存泄漏,這些對象不會被GC所回收,然而它卻占用內(nèi)存。
關(guān)于內(nèi)存泄露的處理頁就是提高程序的健壯型,因為內(nèi)存泄露是純代碼層面的問題。點擊此處查看內(nèi)存泄漏更多說明。
?
1.3 內(nèi)存溢出和內(nèi)存泄露的聯(lián)系
內(nèi)存泄露會最終會導(dǎo)致內(nèi)存溢出。
相同點:都會導(dǎo)致應(yīng)用程序運(yùn)行出現(xiàn)問題,性能下降或掛起。
不同點:1) 內(nèi)存泄露是導(dǎo)致內(nèi)存溢出的原因之一,內(nèi)存泄露積累起來將導(dǎo)致內(nèi)存溢出。2) 內(nèi)存泄露可以通過完善代碼來避免,內(nèi)存溢出可以通過調(diào)整配置來減少發(fā)生頻率,但無法徹底避免。
?
二、一個Java內(nèi)存泄漏的排查案例
某個業(yè)務(wù)系統(tǒng)在一段時間突然變慢,我們懷疑是因為出現(xiàn)內(nèi)存泄露問題導(dǎo)致的,于是踏上排查之路。
2.1?確定頻繁Full GC現(xiàn)象
首先通過“虛擬機(jī)進(jìn)程狀況工具:jps”找出正在運(yùn)行的虛擬機(jī)進(jìn)程,最主要是找出這個進(jìn)程在本地虛擬機(jī)的唯一ID(LVMID,Local Virtual Machine Identifier),因為在后面的排查過程中都是需要這個LVMID來確定要監(jiān)控的是哪一個虛擬機(jī)進(jìn)程。
同時,對于本地虛擬機(jī)進(jìn)程來說,LVMID與操作系統(tǒng)的進(jìn)程ID(PID,Process Identifier)是一致的,使用Windows的任務(wù)管理器或Unix的ps命令也可以查詢到虛擬機(jī)進(jìn)程的LVMID。
jps命令格式為:
jps [ options ] [ hostid ]
使用命令如下:
使用jps:jps -l
使用ps:ps aux | grep tomat?找到你需要監(jiān)控的ID(假設(shè)為20954),再利用“虛擬機(jī)統(tǒng)計信息監(jiān)視工具:jstat”監(jiān)視虛擬機(jī)各種運(yùn)行狀態(tài)信息。
?
jstat命令格式為:
jstat [ option vmid [interval[s|ms] [count]] ]
使用命令如下:
jstat -gcutil 20954 1000
意思是每1000毫秒查詢一次,一直查。gcutil的意思是已使用空間站總空間的百分比。
結(jié)果如下圖:
?
?
jstat執(zhí)行結(jié)果
查詢結(jié)果表明:這臺服務(wù)器的新生代Eden區(qū)(E,表示Eden)使用了28.30%(最后)的空間,兩個Survivor區(qū)(S0、S1,表示Survivor0、Survivor1)分別是0和8.93%,老年代(O,表示Old)使用了87.33%。程序運(yùn)行以來共發(fā)生Minor GC(YGC,表示Young GC)101次,總耗時1.961秒,發(fā)生Full GC(FGC,表示Full GC)7次,Full GC總耗時3.022秒,總的耗時(GCT,表示GC Time)為4.983秒。
?
2.2?找出導(dǎo)致頻繁Full GC的原因
分析方法通常有兩種:
1)把堆dump下來再用MAT等工具進(jìn)行分析,但dump堆要花較長的時間,并且文件巨大,再從服務(wù)器上拖回本地導(dǎo)入工具,這個過程有些折騰,不到萬不得已最好別這么干。
2)更輕量級的在線分析,使用“Java內(nèi)存影像工具:jmap”生成堆轉(zhuǎn)儲快照(一般稱為headdump或dump文件)。
jmap命令格式:
jmap [ option ] vmid
使用命令如下:
jmap -histo:live 20954
查看存活的對象情況,如下圖所示:
?
?
存活對象
按照一位IT友的說法,數(shù)據(jù)不正常,十有八九就是泄露的。在我這個圖上對象還是挺正常的。
我在網(wǎng)上找了一位博友的不正常數(shù)據(jù),如下:
?
可以看出HashTable中的元素有5000多萬,占用內(nèi)存大約1.5G的樣子。這肯定不正常。
?
2.3 定位到代碼
定位帶代碼,有很多種方法,比如前面提到的通過MAT查看Histogram即可找出是哪塊代碼。——我以前是使用這個方法。也可以使用BTrace,我沒有使用過。
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎勵來咯,堅持創(chuàng)作打卡瓜分現(xiàn)金大獎總結(jié)
以上是生活随笔為你收集整理的一次恐怖的 Java 内存泄漏排查实战的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 报告称被马斯克收购一年后,社交媒体 X
- 下一篇: 一道非常棘手的 Java 面试题:i++