1. 程式人生 > >JVM總結-記憶體監視手段及各區域記憶體溢位解決

JVM總結-記憶體監視手段及各區域記憶體溢位解決

引言

本文僅關注一些常見的虛擬機器記憶體監視手段,以及JVM執行時資料區各個部分記憶體溢位的發生和對應的解決方案,總體來說屬於概括性總結,涉及相對不是很深入,目的是讓自己和其它初學者有一個框架性、概念性的瞭解,當遇到問題時有跡可循、不至於不知所措。

一、虛擬機器記憶體監視手段

虛擬機器常出現的問題包括:記憶體洩露、記憶體溢位、頻繁GC導致效能下降等,導致這些問題的原因可以通過下面虛擬機器記憶體監視手段來進行分析,具體實施時可能需要靈活選擇,同時藉助兩種甚至更多的手段來共同分析。

比如GC日誌可以分析出哪些GC較為頻繁導致效能下降、是否發生記憶體洩露。jstat工具和GC日誌類似,同樣可以檢視GC情況、分析是否發生記憶體洩露。判斷髮生記憶體洩露後,可以通過jmap工具和MAT等分析工具的結合檢視虛擬機器記憶體快照,分析發生記憶體洩露的原因。記憶體溢位快照可以分析出記憶體溢位發生的原因等。

GC日誌記錄

將JVM每次進行GC的情況記錄下來,通過觀察GC日誌可以看出來GC的頻度、以及每次GC都回收了哪些區域的記憶體,根據這些資訊為依據來調整JVM相關設定,可以減少Minor GC的頻率以及Full GC的次數,還可以判斷是否有記憶體洩露發生。

下面是常見的GC日誌輸出引數:

u  -verbose.gc:顯示GC的操作內容。開啟它,可以顯示最忙和最空閒收集行為發生的時間、收集前後的記憶體大小、收集需要的時間等。

u  -XX:+printGCdetails:詳細瞭解GC中的變化。

u  -XX:+PrintGCTimeStamps:瞭解垃圾收集發生的時間,自JVM啟動以後以秒計量。

u  -XX:+PrintHeapAtGC:瞭解堆的更詳細的資訊。

u  -Xloggc:[file]:將GC資訊輸出到單獨的檔案中

jstat:虛擬機器統計資訊監控工具

實時監視虛擬機器執行時的類裝載情況、各部分記憶體佔用情況、GC情況、JIT編譯情況等。

例:每隔250ms查詢一次程序2211的垃圾收集情況,查詢50次

步驟①:jps列出本機所有執行的jvm例項,獲取jvm的pid

步驟②:jstat實時監控gc情況,jstat –gc 2211 250 50

其他引數包括:

         -class監視類裝載、解除安裝數量、總空間以及類裝載所耗費時間

         -gccapacity監視內容與-gc相同,輸出主要關注堆各個區域用到的最大、最小空間

         -gcutil監視內同與-gc相同,輸出主要關注堆各個區域已使用空間所佔總空間百分比

         -gcnew監視新生代GC情況

         -gcold監視舊生代GC情況

jmap:虛擬機器記憶體映像工具

jmap工具可以讓執行中的JVM生成Dump檔案,當JVM記憶體出現問題時可以通過jmap生成快照,分析整個堆,主要經歷兩個步驟:

步驟1:jps列出本機所有執行的jvm例項,獲取jvm的pid

步驟2:使用jmap命令將指定JVM快照匯出為dump檔案

jmap -dump:format=b,file=path/heap.bin PID   

獲得JVM快照的dump檔案之後,可以通過MAT工具進行分析。

MAT(MemoryAnalyzer Tool)工具是eclipse的一個外掛,使用起來非常方便,尤其是在分析大記憶體的dump檔案時,可以非常直觀的看到各個物件在堆空間中所佔用的記憶體大小、類例項數量、物件引用關係、利用OQL物件查詢,以及可以很方便的找出物件GC Roots的相關資訊,最吸引人的是能夠快速為開發人員生成記憶體洩露報表,方便定位和分析問題。

除此之外,jmap還可以查詢finalize執行佇列、java堆和持久代的詳細資訊,比如空間使用率,當前使用的是哪種收集器等。

記憶體溢位快照生成

通過設定JVM引數,可以讓虛擬機發生OutOfMemoryError(OOM)記憶體溢位時自動生成dump檔案,通過分析dump檔案檢視記憶體使用情況可以找到記憶體溢位發生的原因:

-XX:+HeapDumpOnOutOfMemoryError-XX:HeapDumpPath=/path/to/heap/dump

獲得JVM快照的dump檔案之後,可以通過MAT工具進行分析。

二、執行時資料區記憶體溢位

JVM執行時資料區分為以下幾個部分:

其中方法區和堆是所有工作執行緒共享的,而棧、程式計數器和本地方法棧是執行緒私有的。


注:圖片轉自網路

1.程式計數器

作用:指向當前執行緒下一條需要執行的位元組碼指令的地址

記憶體溢位:不會發生

2.虛擬機器棧

作用:由棧幀組成、每個棧幀代表一次方法呼叫,其包含儲存變量表、運算元棧和方法出口三個部分,方法執行完成後該棧幀將被彈出。

記憶體溢位:StackOverflowError和OutOfMemoryError。

溢位原因:

StackOverflowError:如果請求的棧的深度大於虛擬機器所允許的深度,將會丟擲這個異常,如果使用虛擬機器預設引數,一般達到1000到2000這樣的深度沒有問題。

OutOfMemoryError:因為除掉堆記憶體和方法區容量,剩下的記憶體由虛擬機器棧和本地方法棧瓜分,如果剩下的記憶體不足以滿足更多的工作執行緒的執行、或者不足以拓展虛擬機器棧的時候,就會丟擲OutOfMemoryError異常。

解決方法:

針對StackOverflowError:

1.      首先棧溢位會輸出異常資訊,根據資訊檢視對應的方法呼叫是否出現無限呼叫、或者棧幀過大等程式碼邏輯上的問題,通過修改程式碼邏輯解決;

2.      如果確確實實需要更大的棧容量,可以檢查並調大棧容量:-Xss16m。

針對OutOfMemoryError:

1.      首先檢查是否建立過多的執行緒,減少執行緒數

2.      可以通過“減少最大堆容量”或“減少棧容量”來解決。

3.本地方法棧

作用:與虛擬機器棧唯一的不同是虛擬機器棧執行的是java方法,而本地方法棧執行的是本地的C/C++方法

記憶體溢位:StackOverflowError和OutOfMemoryError

溢位原因:同虛擬機器棧

解決方法:同虛擬機器棧

4.堆

作用:所有執行緒共享,存放物件例項

記憶體溢位:OutOfMemoryError:Java heap space

溢位原因:堆中沒有足夠記憶體完成例項分配,並且無法繼續拓展時

解決方法

1.記憶體洩露檢查:首先通過“記憶體溢位快照 + MAT等分析工具”,分析是否存在記憶體洩露現象,檢查時可以懷疑的點比如集合、第三方庫如資料庫連線的使用、new關鍵字相關等。

2.如果沒有記憶體洩露,那麼就是記憶體溢位,所有物件卻是都還需要存活,這個時候就只能調大堆記憶體了:-Xms和-Xmx。

5.方法區

作用:所有執行緒共享,存放已載入的class資訊、常量、靜態變數和即時編譯後的程式碼

記憶體溢位:OutOfMemoryError:PermGen space

溢位原因:方法區沒有足夠記憶體完成記憶體分配存放執行時新載入的class資訊

解決方法

1. 記憶體洩露檢查:檢查是否載入過多class檔案(jar檔案),或者重複載入相同的class檔案(jar檔案)多次

2. 通過-XX:PermSize=64M -XX:MaxPermSize=128M改大方法區大小

6.執行時常量池

作用:方法區的一部分,存放常量

記憶體溢位:OutOfMemoryError:PermGen space

溢位原因:方法區沒有足夠的記憶體完成記憶體分配,存放執行時新建立的常量,比如String類的intern()方法,其作用是如果常量池已經包含一個相同的字串,則返回其引用,否則將此String物件包含的字串新增到常量池中。

解決方法

1. 記憶體洩露檢查:檢查是否建立過多常量

2. 通過-XX:PermSize=64M -XX:MaxPermSize=128M改大方法區大小

7.直接記憶體

作用:不屬於JVM執行時資料區,也不是虛擬機器規範中定義的記憶體區域,JDK1.4引入的NIO中包含通道Channel和緩衝區Buffer,應用程式從通道獲取資料是先經過OS的核心緩衝區,再拷貝至Buffer,因為比較耗時,所以Buffer提供了一種直接操作作業系統緩衝區的方式,即ByteBuffer.allocateDirector(size),這個方法返回DirectByteBuffer應用就是指向這個底層儲存空間關聯的緩衝區,即直接記憶體(native memory),或者叫堆外記憶體

記憶體溢位:OutOfMemoryError

溢位原因:JVM所需記憶體 + 直接記憶體 > 機器實體記憶體(或作業系統級限制),無法動態拓展

判斷方法:記憶體洩露檢查:例如記憶體佔用較高,機器效能驟降,但是通過GC資訊或者jstat發現GC很少,通過jmap獲得快照分析後也沒發現什麼異常,而程式中又直接或者間接地用到了NIO,那麼和可能就是直接記憶體洩露了。

解決方法:分析NIO相關的程式邏輯解決。