轉載自:http://www.blogjava.net/rosen/archive/2010/05/21/321575.htmlphp
前言html
在平時工做過程當中,有時會遇到OutOfMemoryError,咱們知道遇到Error通常代表程序存在着嚴重問題,多是災難性的。因此找出是什麼緣由形成OutOfMemoryError很是重要。如今向你們引薦Eclipse Memory Analyzer tool(MAT),來化解咱們遇到的難題。如未說明,本文均使用Java 5.0 on Windows XP SP3環境。java
爲何用MATwindows
以前的觀點,我認爲使用實時profiling/monitoring之類的工具,用一種很是實時的方式來分析哪裏存在內存泄漏是很正確的。年初使用了某profiler工具測試消息中間件中存在的內存泄漏,發如今吞吐量很高的時候profiler工具本身也沒法響應,這讓人很頭痛。後來瞭解到這樣的工具自己就要消耗性能,且在某些條件下還發現不了泄漏。因此,分析離線數據就很是重要了,MAT正是這樣一款工具。數組
爲什麼會內存溢出緩存
咱們知道JVM根據generation(代)來進行GC,根據下圖所示,一共被分爲young generation(年輕代)、tenured generation(老年代)、permanent generation(永久代, perm gen),perm gen(或稱Non-Heap 非堆)是個異類,稍後會講到。注意,heap空間不包括perm gen。併發
絕大多數的對象都在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慢得多。eclipse
JVM有三種垃圾回收器,分別是throughput collector,用來作並行young generation回收,由參數-XX:+UseParallelGC啓動;concurrent low pause collector,用來作tenured generation併發回收,由參數-XX:+UseConcMarkSweepGC啓動;incremental low pause collector,能夠認爲是默認的垃圾回收器。不建議直接使用某種垃圾回收器,最好讓JVM本身決斷,除非本身有足夠的把握。jsp
Heap中各generation空間是如何劃分的?經過JVM的-Xmx=n參數可指定最大heap空間,而-Xms=n
則是指定
最小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是高負載生產環境的噩夢……
如今來講說異類perm gen,它是JVM用來存儲沒法在Java語言級描述的對象,這些對象分別是類和方法數據(與class loader有關)以及interned strings(字符串駐留)。通常32位OS下perm gen默認64m,可經過參數-XX:MaxPermSize=n指定,JVM Memory Structure一文說,對於這塊區域,沒有更詳細的文獻了,神祕。
回到問題「爲什麼會內存溢出?」。
要回答這個問題又要引出另一個話題,既什麼樣的對象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的起點。
從obj1入手,上圖中藍色節點表明僅僅只有經過obj1才能直接或間接訪問的對象。由於能夠經過GC Roots訪問,因此左圖的obj3不是藍色節點;而在右圖倒是藍色,由於它已經被包含在retained集合內。
因此對於左圖,obj1的retained size是obj一、obj二、obj4的shallow size總和;右圖的retained size是obj一、obj二、obj三、obj4的shallow size總和。obj2的retained size能夠經過相同的方式計算。
Heap Dump
heap dump是特定時間點,java進程的內存快照。有不一樣的格式來存儲這些數據,總的來講包含了快照被觸發時java對象和類在heap中的狀況。因爲快照只是一瞬間的事情,因此heap dump中沒法包含一個對象在什麼時候、何地(哪一個方法中)被分配這樣的信息。
在不一樣平臺和不一樣java版本有不一樣的方式獲取heap dump,而MAT須要的是HPROF格式的heap dump二進制文件。想無需人工干預的話,要這樣配置JVM參數:-XX:-HeapDumpOnOutOfMemoryError,當錯誤發生時,會自動生成heap dump,在生產環境中,只有用這種方式。若是你想本身控制何時生成heap dump,在Windows+JDK6環境中可利用JConsole工具,而在Linux或者Mac OS X環境下都可使用JDK五、6自帶的jmap工具。固然,還能夠配置JVM參數:-XX:+HeapDumpOnCtrlBreak,也就是在控制檯使用Ctrl+Break鍵來生成heap dump。因爲我是windows+JDK5,因此選擇了-XX:-HeapDumpOnOutOfMemoryError這種方式,更多配置請參考MAT Wiki。
參考資料
Strong,Soft,Weak,Phantom Reference
Tuning Garbage Collection with the 5.0 Java[tm] Virtual Machine