天天看點

性能分析工具之-- Eclipse Memory Analyzer tool(MAT)(一)

前言

在平時工作過程中,有時會遇到outofmemoryerror,我們知道遇到error一般表明程式存在着嚴重問題,可能是災難性的。是以找出是什麼原因造成outofmemoryerror非常重要。現在向大家引薦eclipse memory analyzer tool(mat),來化解我們遇到的難題。如未說明,本文均使用java 5.0 on windows xp sp3環境。

為什麼用mat

之前的觀點,我認為使用實時profiling/monitoring之類的工具,用一種非常實時的方式來分析哪裡存在記憶體洩漏是很正确的。年初使用了某profiler工具測試消息中間件中存在的記憶體洩漏,發現在吞吐量很高的時候profiler工具自己也無法響應,這讓人很頭痛。後來了解到這樣的工具本身就要消耗性能,且在某些條件下還發現不了洩漏。是以,分析離線資料就非常重要了,mat正是這樣一款工具。

為何會記憶體溢出

我們知道jvm根據generation(代)來進行gc,根據下圖所示,一共被分為young generation(年輕代)、tenured generation(老年代)、permanent generation(永久代, perm gen),perm gen(或稱non-heap 非堆)是個異類,稍後會講到。注意,heap空間不包括perm gen。

性能分析工具之-- Eclipse Memory Analyzer tool(MAT)(一)

絕大多數的對象都在young generation被配置設定,也在young generation被收回,當young generation的空間被填滿,gc會進行minor collection(次回收),這次回收不涉及到heap中的其他generation,minor collection根據weak generational hypothesis(弱年代假設)來假設young generation中大量的對象都是垃圾需要回收,minor collection的過程會非常快。young generation中未被回收的對象被轉移到tenured

generation,然而tenured generation也會被填滿,最終觸發major collection(主回收),這次回收針對整個heap,由于涉及到大量對象,是以比minor collection慢得多。

jvm有三種垃圾回收器,分别是throughput collector,用來做并行young generation回收,由參數-xx:+useparallelgc啟動;concurrent low pause collector,用來做tenured generation并發回收,由參數-xx:+useconcmarksweepgc啟動;incremental low pause collector,可以認為是預設的垃圾回收器。不建議直接使用某種垃圾回收器,最好讓jvm自己決斷,除非自己有足夠的把握。

heap中各generation空間是如何劃分的?通過jvm的-xmx=n參數可指定最大heap空間,而<code>-xms=n</code><code>則是指定</code>最小heap空間。在jvm初始化的時候,如果最小heap空間小于最大heap空間的話,如上圖所示jvm會把未用到的空間标注為virtual。除了這兩個參數還有-xx:minheapfreeratio=n和 -xx:maxheapfreeratio=n來分别控制最大、最小的剩餘空間與活動對象之比例。在32位solaris sparc作業系統下,預設值如下,在32位windows

xp下,預設值也差不多。

參數

預設值

minheapfreeratio

40

maxheapfreeratio

70

-xms

3670k

-xmx

64m

由于tenured generation的major collection較慢,是以tenured generation空間小于young generation的話,會造成頻繁的major collection,影響效率。server jvm預設的young generation和tenured generation空間比例為1:2,也就是說young generation的eden和survivor空間之和是整個heap(當然不包括perm gen)的三分之一,該比例可以通過-xx:newratio=n參數來控制,而client

jvm預設的-xx:newratio是8。至于調整young generation空間大小的newsize=n和maxnewsize=n參數就不講了,請參考後面的資料。

young generation中幸存的對象被轉移到tenured generation,但不幸的是concurrent collector線程在這裡進行major collection,而在回收任務結束前空間被耗盡了,這時将會發生full collections(full gc),整個應用程式都會停止下來直到回收完成。full gc是高負載生産環境的噩夢……

回到問題“為何會記憶體溢出?”。

要回答這個問題又要引出另外一個話題,既什麼樣的對象gc才會回收?當然是gc發現通過任何reference chain(引用鍊)無法通路某個對象的時候,該對象即被回收。名詞gc roots正是分析這一過程的起點,例如jvm自己確定了對象的可到達性(那麼jvm就是gc roots),是以gc roots就是這樣在記憶體中保持對象可到達性的,一旦不可到達,即被回收。通常gc roots是一個在current thread(目前線程)的call stack(調用棧)上的對象(例如方法參數和局部變量),或者是線程自身或者是system

class loader(系統類加載器)加載的類以及native code(本地代碼)保留的活動對象。是以gc roots是分析對象為何還存活于記憶體中的利器。知道了什麼樣的對象gc才會回收後,再來學習下對象引用都包含哪些吧。

從最強到最弱,不同的引用(可到達性)級别反映了對象的生命周期。

l  strong ref(強引用):通常我們編寫的代碼都是strong ref,于此對應的是強可達性,隻有去掉強可達,對象才被回收。

l  soft ref(軟引用):對應軟可達性,隻要有足夠的記憶體,就一直保持對象,直到發現記憶體吃緊且沒有strong ref時才回收對象。一般可用來實作緩存,通過java.lang.ref.softreference類實作。

l  weak ref(弱引用):比soft ref更弱,當發現不存在strong ref時,立刻回收對象而不必等到記憶體吃緊的時候。通過java.lang.ref.weakreference和java.util.weakhashmap類實作。

l  phantom ref(虛引用):根本不會在記憶體中保持任何對象,你隻能使用phantom ref本身。一般用于在進入finalize()方法後進行特殊的清理過程,通過java.lang.ref.phantomreference實作。

有了上面的種種我相信很容易就能把heap和perm gen撐破了吧,是的利用strong ref,存儲大量資料,直到heap撐破;利用interned strings(或者class loader加載大量的類)把perm gen撐破。

關于shallow size、retained size

shallow size就是對象本身占用記憶體的大小,不包含對其他對象的引用,也就是對象頭加成員變量(不是成員變量的值)的總和。在32位系統上,對象頭占用8位元組,int占用4位元組,不管成員變量(對象或數組)是否引用了其他對象(執行個體)或者指派為null它始終占用4位元組。故此,對于string對象執行個體來說,它有三個int成員(3*4=12位元組)、一個char[]成員(1*4=4位元組)以及一個對象頭(8位元組),總共3*4 +1*4+8=24位元組。根據這一原則,對string a=”rosen jiang”來說,執行個體a的shallow

size也是24位元組(很多人對此有争議,請看官甄别并留言給我)。

retained size是該對象自己的shallow size,加上從該對象能直接或間接通路到對象的shallow size之和。換句話說,retained size是該對象被gc之後所能回收到記憶體的總和。為了更好的了解retained size,不妨看個例子。

把記憶體中的對象看成下圖中的節點,并且對象和對象之間互相引用。這裡有一個特殊的節點gc roots,正解!這就是reference chain的起點。

性能分析工具之-- Eclipse Memory Analyzer tool(MAT)(一)
性能分析工具之-- Eclipse Memory Analyzer tool(MAT)(一)

從obj1入手,上圖中藍色節點代表僅僅隻有通過obj1才能直接或間接通路的對象。因為可以通過gc roots通路,是以左圖的obj3不是藍色節點;而在右圖卻是藍色,因為它已經被包含在retained集合内。

是以對于左圖,obj1的retained size是obj1、obj2、obj4的shallow size總和;右圖的retained size是obj1、obj2、obj3、obj4的shallow size總和。obj2的retained size可以通過相同的方式計算。

heap dump

 heap dump是特定時間點,java程序的記憶體快照。有不同的格式來存儲這些資料,總的來說包含了快照被觸發時java對象和類在heap中的情況。由于快照隻是一瞬間的事情,是以heap dump中無法包含一個對象在何時、何地(哪個方法中)被配置設定這樣的資訊。

在不同平台和不同java版本有不同的方式擷取heap dump,而mat需要的是hprof格式的heap dump二進制檔案。

如何擷取heap dump檔案?

通常來說,隻要你設定了如下所示的 jvm 參數:

-xx:+heapdumponoutofmemoryerror

jvm 就會在發生記憶體洩露時抓拍下當時的記憶體狀态,也就是我們想要的堆轉儲檔案。

如果你不想等到發生崩潰性的錯誤時才獲得堆轉儲檔案,也可以通過設定如下 jvm 參數來按需擷取堆轉儲檔案。

-xx:+heapdumponctrlbreak  (轉注:jdk6以上已不支援)

jmap -dump:format=b,file=&lt;dumpfile&gt; &lt;pid&gt;

解釋:format=b--&gt;指定格式為二進制;file=&lt;dumpfile&gt;--&gt;指定檔案名稱,自定義;&lt;pid&gt; --&gt;程序id

參考資料

<a target="_blank" href="http://wiki.eclipse.org/index.php/memoryanalyzer">mat wiki</a>

<a target="_blank" href="http://mindprod.com/jgloss/interned.html">interned strings</a>

<a target="_blank" href="http://mindprod.com/jgloss/weak.html">strong,soft,weak,phantom reference</a>

<a target="_blank" href="http://java.sun.com/docs/hotspot/gc5.0/gc_tuning_5.html">tuning garbage collection with the 5.0 java[tm] virtual machine</a>

<a target="_blank" href="http://mindprod.com/jgloss/permgen.html">permanent generation</a>

<a target="_blank" href="http://blog.csdn.net/xtyyumi301/archive/2008/10/04/3015493.aspx">understanding weak references譯文</a>

<a target="_blank" href="http://java.sun.com/javase/technologies/hotspot/vmoptions.jsp">java hotspot vm options</a>

<a target="_blank" href="http://www.yourkit.com/docs/90/help/sizes.jsp">shallow and retained sizes</a>

<a target="_blank" href="http://www.yourkit.com/docs/kb/sizes.jsp">jvm memory structure</a>

<a target="_blank" href="http://www.yourkit.com/docs/90/help/gc_roots.jsp">gc roots</a>

繼續閱讀