最近在看《深入了解Java虛拟機:JVM進階特性與最佳實踐》(第二版)這本書,理論+實踐結合,深入淺出,強烈推薦給大家。
這兩天對JVM内容進行了一個讨論,讨論的内容主要包括如下幾個方面。
1)記憶體溢出和記憶體洩露的介紹? 2)如何排查和處理記憶體洩露?
一、記憶體溢出和記憶體洩露
一種通俗的說法。
1、記憶體溢出:你申請了10個位元組的空間,但是你在這個空間寫入11或以上位元組的資料,出現溢出。
2、記憶體洩漏:你用new申請了一塊記憶體,後來很長時間都不再使用了(按理應該釋放),但是因為一直被某個或某些執行個體所持有導緻 GC 不能回收,也就是該被釋放的對象沒有釋放。點選此處檢視記憶體洩漏更多說明。
下面具體介紹。
1.1 記憶體溢出
java.lang.OutOfMemoryError,是指程式在申請記憶體時,沒有足夠的記憶體空間供其使用,出現OutOfMemoryError。點選此處檢視記憶體洩漏更多說明。
産生原因
産生該錯誤的原因主要包括:
JVM記憶體過小。
程式不嚴密,産生了過多的垃圾。
程式展現
一般情況下,在程式上的展現為
記憶體中加載的資料量過于龐大,如一次從資料庫取出過多資料。
集合類中有對對象的引用,使用完後未清空,使得JVM不能回收。
代碼中存在死循環或循環産生過多重複的對象實體。
使用的第三方軟體中的BUG。
啟動參數記憶體值設定的過小。
錯誤提示
此錯誤常見的錯誤提示:

解決方法
*1)增加JVM的記憶體大小 *
對于tomcat容器,找到tomcat在電腦中的安裝目錄,進入這個目錄,然後進入bin目錄中,在window環境下找到bin目錄中的catalina.bat,在linux環境下找到catalina.sh。 編輯catalina.bat檔案,找到JAVA_OPTS(具體來說是 set "JAVA_OPTS=%JAVA_OPTS% %LOGGING_MANAGER%")這個選項的位置,這個參數是Java啟動的時候,需要的啟動參數。 也可以在作業系統的環境變量中對JAVA_OPTS進行設定,因為tomcat在啟動的時候,也會讀取作業系統中的環境變量的值,進行加載。 如果是修改了作業系統的環境變量,需要重新開機機器,再重新開機tomcat,如果修改的是tomcat配置檔案,需要将配置檔案儲存,然後重新開機tomcat,設定就能生效了。
2)優化程式,釋放垃圾
主要思路就是避免程式展現上出現的情況。避免死循環,防止一次載入太多的資料,提高程式健壯型及時釋放。是以,從根本上解決Java記憶體溢出的唯一方法就是修改程式,及時地釋放沒用的對象,釋放記憶體空間。
1.2 **[記憶體洩露**](http://mp.weixin.qq.com/s?__biz=MzI3ODcxMzQzMw==&mid=2247484867&idx=1&sn=fe5ef395be8f9e5bac5be73b3a88a51f&chksm=eb5380f5dc2409e34510dd17c7368a50489ec20ec72a3837db13cb847d589cad576d476ef6e7&scene=21#wechat_redirect)
Memory Leak,是指程式在申請記憶體後,無法釋放已申請的記憶體空間,一次記憶體洩露危害可以忽略,但記憶體洩露堆積後果很嚴重,無論多少記憶體,遲早會被占光。
在Java中,記憶體洩漏就是存在一些被配置設定的對象,這些對象有下面兩個特點。
1)首先,這些對象是可達的,即在有向圖中,存在通路可以與其相連; 2)其次,這些對象是無用的,即程式以後不會再使用這些對象。
如果對象滿足這兩個條件,這些對象就可以判定為Java中的記憶體洩漏,這些對象不會被GC所回收,然而它卻占用記憶體。
關于記憶體洩露的處理頁就是提高程式的健壯型,因為記憶體洩露是純代碼層面的問題。點選此處檢視記憶體洩漏更多說明。
1.3 記憶體溢出和記憶體洩露的聯系
記憶體洩露會最終會導緻記憶體溢出。
相同點:都會導緻應用程式運作出現問題,性能下降或挂起。 不同點:1) 記憶體洩露是導緻記憶體溢出的原因之一,記憶體洩露積累起來将導緻記憶體溢出。2) 記憶體洩露可以通過完善代碼來避免,記憶體溢出可以通過調整配置來減少發生頻率,但無法徹底避免。
二、一個Java記憶體洩漏的排查案例
某個業務系統在一段時間突然變慢,我們懷疑是因為出現記憶體洩露問題導緻的,于是踏上排查之路。
2.1确定頻繁Full GC現象
首先通過“虛拟機程序狀況工具:jps”找出正在運作的虛拟機程序,最主要是找出這個程序在本地虛拟機的唯一ID(LVMID,Local Virtual Machine Identifier),因為在後面的排查過程中都是需要這個LVMID來确定要監控的是哪一個虛拟機程序。
同時,對于本地虛拟機程序來說,LVMID與作業系統的程序ID(PID,Process Identifier)是一緻的,使用Windows的任務管理器或Unix的ps指令也可以查詢到虛拟機程序的LVMID。
jps指令格式為: jps [ options ] [ hostid ] 使用指令如下: 使用jps:jps -l
使用ps:ps aux | grep tomat找到你需要監控的ID(假設為20954),再利用“虛拟機統計資訊監視工具:jstat”監視虛拟機各種運作狀态資訊。
jstat指令格式為: jstat [ option vmid [interval[s|ms] [count]] ] 使用指令如下: jstat -gcutil 20954 1000 意思是每1000毫秒查詢一次,一直查。gcutil的意思是已使用空間站總空間的百分比。
結果如下圖:
jstat執行結果
查詢結果表明:這台伺服器的新生代Eden區(E,表示Eden)使用了28.30%(最後)的空間,兩個Survivor區(S0、S1,表示Survivor0、Survivor1)分别是0和8.93%,老年代(O,表示Old)使用了87.33%。程式運作以來共發生Minor GC(YGC,表示Young GC)101次,總耗時1.961秒,發生Full GC(FGC,表示Full GC)7次,Full GC總耗時3.022秒,總的耗時(GCT,表示GC Time)為4.983秒。
2.2 **[找出導緻頻繁Full GC的原因**](http://mp.weixin.qq.com/s?__biz=MzI3ODcxMzQzMw==&mid=2247484867&idx=1&sn=fe5ef395be8f9e5bac5be73b3a88a51f&chksm=eb5380f5dc2409e34510dd17c7368a50489ec20ec72a3837db13cb847d589cad576d476ef6e7&scene=21#wechat_redirect)
分析方法通常有兩種:
1)把堆dump下來再用MAT等工具進行分析,但dump堆要花較長的時間,并且檔案巨大,再從伺服器上拖回本地導入工具,這個過程有些折騰,不到萬不得已最好别這麼幹。
2)更輕量級的線上分析,使用“Java記憶體影像工具:jmap”生成堆轉儲快照(一般稱為headdump或dump檔案)。
jmap指令格式: jmap [ option ] vmid 使用指令如下: jmap -histo:live 20954 檢視存活的對象情況,如下圖所示:
可以看出HashTable中的元素有5000多萬,占用記憶體大約1.5G的樣子。這肯定不正常。
2.3 定位到代碼
定位帶代碼,有很多種方法,比如前面提到的通過MAT檢視Histogram即可找出是哪塊代碼。——我以前是使用這個方法。也可以使用BTrace,我沒有使用過。
近期熱文推薦:
1.600+ 道 Java面試題及答案整理(2021最新版)
2.終于靠開源項目弄到 IntelliJ IDEA 激活碼了,真香!
3.阿裡 Mock 工具正式開源,幹掉市面上所有 Mock 工具!
4.Spring Cloud 2020.0.0 正式釋出,全新颠覆性版本!
5.《Java開發手冊(嵩山版)》最新釋出,速速下載下傳!
覺得不錯,别忘了随手點贊+轉發哦!