1. 程式人生 > >【轉】java記憶體洩漏的定位與分析

【轉】java記憶體洩漏的定位與分析

1、為什麼會發生記憶體洩漏

Java如何檢測內在洩漏呢?我們需要一些工具進行檢測,並發現記憶體洩漏問題,不然很容易發生down機問題。

編寫java程式最為方便的地方就是我們不需要管理記憶體的分配和釋放,一切由jvm來進行處理,當java物件不再被應用時,等到堆記憶體不夠用時,jvm會進行垃圾回收,清除這些物件佔用的堆記憶體空間,如果物件一直被應用,jvm無法對其進行回收,建立新的物件時,無法從Heap中獲取足夠的記憶體分配給物件,這時候就會導致記憶體溢位。而出現記憶體洩露的地方,一般是不斷的往容器中存放物件,而容器沒有相應的大小限制或清除機制。容易導致記憶體溢位。 當伺服器應用佔用了過多記憶體的時候,如何快速定位問題呢?現在,Eclipse MAT的出現使這個問題變得非常簡單。EclipseMAT是著名的SAP公司貢獻的一個工具,可以在Eclipse網站下載到它,完全免費的。 要定位問題,首先你需要獲取伺服器jvm某刻記憶體快照。jdk自帶的jmap可以獲取記憶體某一時刻的快照,匯出為dmp檔案後,就可以用Eclipse MAT來分析了,找出是那個物件使用記憶體過多。

2、記憶體洩漏的現象:

常常地,程式記憶體洩漏的最初跡象發生在出錯之後,在你的程式中得到一個OutOfMemoryError。這種典型的情況發生在產品環境中,而在那裡,你希望記憶體洩漏盡可能的少,除錯的可能性也達到最小。也許你的測試環境和產品的系統環境不盡相同,導致洩露的只會在產品中暴露。這種情況下,你需要一個低負荷的工具來監聽和尋找記憶體洩漏。同時,你還需要把這個工具同你的系統聯絡起來,而不需要重新啟動他或者機械化你的程式碼。也許更重要的是,當你做分析的時候,你需要能夠同工具分離而使得系統不會受到干擾。 一個OutOfMemoryError常常是記憶體洩漏的一個標誌,有可能應用程式的確用了太多的記憶體;這個時候,你既不能增加JVM的堆的數量,也不能改變你的程式而使得他減少記憶體使用。但是,在大多數情況下,一個OutOfMemoryError是記憶體洩漏的標誌。一個解決辦法就是繼續監聽GC的活動,看看隨時間的流逝,記憶體使用量是否會增加,如果有,程式中一定存在記憶體洩漏。

3、發現記憶體洩漏

1. jstat -gc pid

可以顯示gc的資訊,檢視gc的次數,及時間。

其中最後五項,分別是young gc的次數,young gc的時間,full gc的次數,full gc的時間,gc的總時間。

2.jstat -gccapacity pid

可以顯示,VM記憶體中三代(young,old,perm)物件的使用和佔用大小,

如:PGCMN顯示的是最小perm的記憶體使用量,PGCMX顯示的是perm的記憶體最大使用量,

PGC是當前新生成的perm記憶體佔用量,PC是但前perm記憶體佔用量。

其他的可以根據這個類推,OC是old內純的佔用量。

3.jstat -gcutil pid

統計gc資訊統計。

4.jstat -gcnew pid

年輕代物件的資訊。

5.jstat -gcnewcapacity pid

年輕代物件的資訊及其佔用量。

6.jstat -gcold pid

old代物件的資訊。

7.stat -gcoldcapacity pid

old代物件的資訊及其佔用量。

8.jstat -gcpermcapacity pid

perm物件的資訊及其佔用量。

9.jstat -class pid

顯示載入class的數量,及所佔空間等資訊。 10.jstat -compiler pid

顯示VM實時編譯的數量等資訊。

11.stat -printcompilation pid

當前VM執行的資訊。

一些術語的中文解釋:

S0C:年輕代中第一個survivor(倖存區)的容量(位元組) S1C:年輕代中第二個survivor(倖存區)的容量(位元組) S0U:年輕代中第一個survivor(倖存區)目前已使用空間(位元組) S1U:年輕代中第二個survivor(倖存區)目前已使用空間(位元組) EC:年輕代中Eden(伊甸園)的容量(位元組) EU:年輕代中Eden(伊甸園)目前已使用空間(位元組) OC:Old代的容量(位元組) OU:Old代目前已使用空間(位元組) PC:Perm(持久代)的容量(位元組) PU:Perm(持久代)目前已使用空間(位元組) YGC:從應用程式啟動到取樣時年輕代中gc次數 YGCT:從應用程式啟動到取樣時年輕代中gc所用時間(s) FGC:從應用程式啟動到取樣時old代(全gc)gc次數 FGCT:從應用程式啟動到取樣時old代(全gc)gc所用時間(s) GCT:從應用程式啟動到取樣時gc用的總時間(s)

NGCMN:年輕代(young)中初始化(最小)的大小(位元組)

NGCMX:年輕代(young)的最大容量(位元組)

NGC:年輕代(young)中當前的容量(位元組)

OGCMN:old代中初始化(最小)的大小(位元組)

OGCMX:old代的最大容量(位元組)

OGC:old代當前新生成的容量(位元組)

PGCMN:perm代中初始化(最小)的大小(位元組)

PGCMX:perm代的最大容量(位元組)

PGC:perm代當前新生成的容量(位元組)

S0:年輕代中第一個survivor(倖存區)已使用的佔當前容量百分比

S1:年輕代中第二個survivor(倖存區)已使用的佔當前容量百分比

E:年輕代中Eden(伊甸園)已使用的佔當前容量百分比

O:old代已使用的佔當前容量百分比

P:perm代已使用的佔當前容量百分比

S0CMX:年輕代中第一個survivor(倖存區)的最大容量(位元組)

S1CMX:年輕代中第二個survivor(倖存區)的最大容量(位元組)

ECMX:年輕代中Eden(伊甸園)的最大容量(位元組)

DSS:當前需要survivor(倖存區)的容量(位元組)(Eden區已滿)

TT:持有次數限制

MTT:最大持有次數限制

如果定位記憶體洩漏問題我一般使用如下命令:

Jstat -gcutil15469 2500 70

[[email protected] logs]# jstat -gcutil 15469 1000 300

S0 S1 E O P YGC YGCT FGC FGCT GCT

0.00 1.46 26.54 4.61 30.14 35 0.872 0 0.000 0.872

0.00 1.46 46.54 4.61 30.14 35 0.872 0 0.000 0.872

0.00 1.46 47.04 4.61 30.14 35 0.872 0 0.000 0.872

0.00 1.46 65.19 4.61 30.14 35 0.872 0 0.000 0.872

0.00 1.46 67.54 4.61 30.14 35 0.872 0 0.000 0.872

0.00 1.46 87.54 4.61 30.14 35 0.872 0 0.000 0.872

0.00 1.46 88.03 4.61 30.14 35 0.872 0 0.000 0.872

1.48 0.00 5.56 4.62 30.14 36 0.874 0 0.000 0.874

1000 代表多久間隔顯示一次,

100 代表顯示一次。

S0 — Heap上的 Survivor space 0 區已使用空間的百分比

S1 — Heap上的 Survivor space 1 區已使用空間的百分比

E — Heap上的 Eden space 區已使用空間的百分比

O — Heap上的 Old space 區已使用空間的百分比

P — Perm space 區已使用空間的百分比

YGC — 從應用程式啟動到取樣時發生 Young GC 的次數

YGCT– 從應用程式啟動到取樣時 Young GC 所用的時間(單位秒)

FGC — 從應用程式啟動到取樣時發生 Full GC 的次數

FGCT– 從應用程式啟動到取樣時 Full GC 所用的時間(單位秒)

GCT — 從應用程式啟動到取樣時用於垃圾回收的總時間(單位秒)

如果有大量的FGC就要查詢是否有記憶體洩漏的問題了,圖中的FGC數量就比較大,並且執行時間較長,這樣就會導致系統的響應時間較長,如果對jvm的記憶體設定較大,那麼執行一次FGC的時間可能會更長。

如果為了更好的證明FGC對伺服器效能的影響,我們可以使用java visualVM來檢視一下:

從上圖可以發現執行FGC的情況,下午3:10分之前是沒有FGC的,之後出現大量的FGC。

上圖是jvm堆記憶體的使用情況,下午3:10分之前的記憶體回收還是比較合理,但是之後大量記憶體無法回收,最後導致記憶體越來越少,導致大量的full gc。

下面我們在看看大量full GC對伺服器效能的影響,下面是我用loadrunner對我們專案進行壓力測試相應時間的截圖:

從圖中可以發現有,在進行full GC後系統的相應時間有了明顯的增加,點選率和吞吐量也有了明顯的下降。所以java記憶體洩漏對系統性能的影響是不可忽視的。

3、定位記憶體洩漏

當然通過上面幾種方法我們可以發現java的記憶體洩漏問題,但是作為一名合格的高階工程師,肯定不甘心就把這樣的結論交給開發,當然這也的結論交給開發,開發也很難定位問題,為了更好的提供自己在公司的地位,我們必須給開發工程師提供更深入的測試結論,下面就來認識一下MemoryAnalyzer.exe。java記憶體洩漏檢查工具利器。

首先我們必須對jvm的堆記憶體進行dump,只有拿到這個檔案我們才能分析出jvm堆記憶體中到底存了些什麼內容,到底在做什麼?

MemoryAnalyzer的使用者我在這裡就不一一說明了,我的部落格裡也有說明,下面就展示我測試的成功圖:

其中深藍色的部分就為記憶體洩漏的部分,java的堆記憶體一共只有481.5M而記憶體洩漏的部分獨自佔有了336.2M所以本次的記憶體洩漏很明顯,那麼我就來看看那個方法導致的記憶體洩漏:

從上圖我們可以發現紅線圈著的方法佔用了堆記憶體的67.75%,如果能把這個測試結果交給開發,開發是不是應該很好定位呢。所以作為一名高階測試工程師,我們需要學習的東西太多。

雖然不確定一定是記憶體洩漏,但是可以準確的告訴開發問題出現的原因,有一定的說服力。