一次恐怖的 Java 內存泄漏排查實戰

做者:小怪聊職場
https://www.jianshu.com/p/454...

最近在看《深刻理解Java虛擬機:JVM高級特性與最佳實踐》(第二版)這本書,理論+實踐結合,深刻淺出,強烈推薦給你們。java

這兩天對JVM內容進行了一個討論,討論的內容主要包括以下幾個方面。linux

1)內存溢出和內存泄露的介紹?
2)如何排查和處理內存泄露?web

1、內存溢出和內存泄露

一種通俗的說法。面試

一、內存溢出:你申請了10個字節的空間,可是你在這個空間寫入11或以上字節的數據,出現溢出。spring

二、內存泄漏:你用new申請了一塊內存,後來很長時間都再也不使用了(按理應該釋放),可是由於一直被某個或某些實例所持有致使 GC 不能回收,也就是該被釋放的對象沒有釋放。點擊此處查看內存泄漏更多說明。數據庫

下面具體介紹。tomcat

1.1 內存溢出

java.lang.OutOfMemoryError,是指程序在申請內存時,沒有足夠的內存空間供其使用,出現OutOfMemoryError。點擊此處查看內存泄漏更多說明。服務器

產生緣由intellij-idea

產生該錯誤的緣由主要包括:ide

  • JVM內存太小。
  • 程序不嚴密,產生了過多的垃圾。

程序體現

通常狀況下,在程序上的體現爲

  • 內存中加載的數據量過於龐大,如一次從數據庫取出過多數據。
  • 集合類中有對對象的引用,使用完後未清空,使得JVM不能回收。
  • 代碼中存在死循環或循環產生過多重複的對象實體。
  • 使用的第三方軟件中的BUG。
  • 啓動參數內存值設定的太小。

錯誤提示

此錯誤常見的錯誤提示:

tomcat:java.lang.OutOfMemoryError: PermGen space
tomcat:java.lang.OutOfMemoryError: Java heap space
weblogic:Root cause of ServletException java.lang.OutOfMemoryError
resin:java.lang.OutOfMemoryError
java:java.lang.OutOfMemoryError

解決方法

**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 內存泄露

Memory Leak,是指程序在申請內存後,沒法釋放已申請的內存空間,一次內存泄露危害能夠忽略,但內存泄露堆積後果很嚴重,不管多少內存,早晚會被佔光。

在Java中,內存泄漏就是存在一些被分配的對象,這些對象有下面兩個特色。

1)首先,這些對象是可達的,即在有向圖中,存在通路能夠與其相連;
2)其次,這些對象是無用的,即程序之後不會再使用這些對象。

若是對象知足這兩個條件,這些對象就能夠斷定爲Java中的內存泄漏,這些對象不會被GC所回收,然而它卻佔用內存。

關於內存泄露的處理頁就是提升程序的健壯型,由於內存泄露是純代碼層面的問題。點擊此處查看內存泄漏更多說明。

1.3 內存溢出和內存泄露的聯繫

內存泄露會最終會致使內存溢出。

相同點:都會致使應用程序運行出現問題,性能降低或掛起。
不一樣點:1) 內存泄露是致使內存溢出的緣由之一,內存泄露積累起來將致使內存溢出。2) 內存泄露能夠經過完善代碼來避免,內存溢出能夠經過調整配置來減小發生頻率,但沒法完全避免。

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的緣由

分析方法一般有兩種:

1)把堆dump下來再用MAT等工具進行分析,但dump堆要花較長的時間,而且文件巨大,再從服務器上拖回本地導入工具,這個過程有些折騰,不到萬不得已最好別這麼幹。

2)更輕量級的在線分析,使用「Java內存影像工具:jmap」生成堆轉儲快照(通常稱爲headdump或dump文件)。

jmap命令格式:
jmap [ option ] vmid
使用命令以下:
jmap -histo:live 20954
查看存活的對象狀況,以下圖所示:

存活對象

按照一位IT友的說法,數據不正常,十有八九就是泄露的。在我這個圖上對象仍是挺正常的。

我在網上找了一位博友的不正常數據,以下:

能夠看出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開發手冊(嵩山版)》最新發布,速速下載!

以爲不錯,別忘了隨手點贊+轉發哦!

相關文章
相關標籤/搜索