Java垃圾回收概況
Java GC(Garbage Collection,垃圾收集,垃圾回收)機制,是Java與C++/C的主要差別之一,作為Java開發者,一般不需要專門編寫記憶體回收和垃圾清理代 碼,對記憶體洩露和溢出的問題,也不需要像C程式員那樣戰戰兢兢。這是因為在Java虛拟機中,存在自動記憶體管理和垃圾清掃機制。概括地說,該機制對 JVM(Java Virtual Machine)中的記憶體進行标記,并确定哪些記憶體需要回收,根據一定的回收政策,自動的回收記憶體,永不停息(Nerver Stop)的保證JVM中的記憶體空間,放置出現記憶體洩露和溢出問題。
關于JVM,需要說明一下的是,目前使用最多的Sun公司的JDK中,自從 1999年的JDK1.2開始直至現在仍在廣泛使用的JDK6,其中預設的虛拟機都是HotSpot。2009年,Oracle收購Sun,加上之前收購 的EBA公司,Oracle擁有3大虛拟機中的兩個:JRockit和HotSpot,Oracle也表明了想要整合兩大虛拟機的意圖,但是目前在新釋出 的JDK7中,預設的虛拟機仍然是HotSpot,是以本文中預設介紹的虛拟機都是HotSpot,相關機制也主要是指HotSpot的GC機制。
Java GC機制主要完成3件事:确定哪些記憶體需要回收,确定什麼時候需要執行GC,如何執行GC。經過這麼長時間的發展(事實上,在Java語言出現之前,就有 GC機制的存在,如Lisp語言),Java GC機制已經日臻完善,幾乎可以自動的為我們做絕大多數的事情。然而,如果我們從事較大型的應用軟體開發,曾經出現過記憶體優化的需求,就必定要研究 Java GC機制。
學習Java GC機制,可以幫助我們在日常工作中排查各種記憶體溢出或洩露問題,解決性能瓶頸,達到更高的并發量,寫出更高效的程式。
我們将從4個方面學習Java GC機制,1,記憶體是如何配置設定的;2,如何保證記憶體不被錯誤回收(即:哪些記憶體需要回收);3,在什麼情況下執行GC以及執行GC的方式;4,如何監控和優化GC機制。
了解Java GC機制,必須先清楚在JVM中記憶體區域的劃分。在Java運作時的資料區裡,由JVM管理的記憶體區域分為下圖幾個子產品:
其中:
1,程式計數器(Program Counter Register):程式計數器是一個比較小的記憶體區域,用于訓示目前線程所執行的位元組碼執行到了第幾行,可以了解為是目前線程的行号訓示器。位元組碼解釋器在工作時,會通過改變這個計數器的值來取下一條語句指令。
每個程式計數器隻用來記錄一個線程的行号,是以它是線程私有(一個線程就有一個程式計數器)的。
如果程式執行的是一個Java方法,則計數器記錄的是正在執行的虛拟機位元組碼指令位址;如果正在執行的是一個本地(native,由C語言編寫 完成)方法,則計數器的值為Undefined,由于程式計數器隻是記錄目前指令位址,是以不存在記憶體溢出的情況,是以,程式計數器也是所有JVM記憶體區 域中唯一一個沒有定義OutOfMemoryError的區域。
2,虛拟機棧(JVM Stack):一個線程的每個方法在執行的同時,都會建立一個棧幀(Statck Frame),棧幀中存儲的有局部變量表、操作站、動态連結、方法出口等,當方法被調用時,棧幀在JVM棧中入棧,當方法執行完成時,棧幀出棧。
局部變量表中存儲着方法的相關局部變量,包括各種基本資料類型,對象的引用,傳回位址等。在局部變量表中,隻有long和double類型會占 用2個局部變量空間(Slot,對于32位機器,一個Slot就是32個bit),其它都是1個Slot。需要注意的是,局部變量表是在編譯時就已經确定 好的,方法運作所需要配置設定的空間在棧幀中是完全确定的,在方法的生命周期内都不會改變。
虛拟機棧中定義了兩種異常,如果線程調用的棧深度大于虛拟機允許的最大深度,則抛出StatckOverFlowError(棧溢出);不過多 數Java虛拟機都允許動态擴充虛拟機棧的大小(有少部分是固定長度的),是以線程可以一直申請棧,知道記憶體不足,此時,會抛出 OutOfMemoryError(記憶體溢出)。
每個線程對應着一個虛拟機棧,是以虛拟機棧也是線程私有的。
3,本地方法棧(Native Method Statck):本地方法棧在作用,運作機制,異常類型等方面都與虛拟機棧相同,唯一的差別是:虛拟機棧是執行Java方法的,而本地方法棧是用來執行native方法的,在很多虛拟機中(如Sun的JDK預設的HotSpot虛拟機),會将本地方法棧與虛拟機棧放在一起使用。
本地方法棧也是線程私有的。
4,堆區(Heap):堆區是了解Java GC機制最重要的區域,沒有之一。在JVM所管理的記憶體中,堆區是最大的一塊,堆區也是Java GC機制所管理的主要記憶體區域,堆區由所有線程共享,在虛拟機啟動時建立。堆區的存在是為了存儲對象執行個體,原則上講,所有的對象都在堆區上配置設定記憶體(不過現代技術裡,也不是這麼絕對的,也有棧上直接配置設定的)。
一般的,根據Java虛拟機規範規定,堆記憶體需要在邏輯上是連續的(在實體上不需要),在實作時,可以是固定大小的,也可以是可擴充的,目前主 流的虛拟機都是可擴充的。如果在執行垃圾回收之後,仍沒有足夠的記憶體配置設定,也不能再擴充,将會抛出OutOfMemoryError:Java heap space異常。
關于堆區的内容還有很多,将在下節“Java記憶體配置設定機制”中詳細介紹。
5,方法區(Method Area):在Java虛拟機規範中,将方法區作為堆的一個邏輯部分來對待,但事實 上,方法區并不是堆(Non-Heap);另外,不少人的部落格中,将Java GC的分代收集機制分為3個代:青年代,老年代,永久代,這些作者将方法區定義為“永久代”,這是因為,對于之前的HotSpot Java虛拟機的實作方式中,将分代收集的思想擴充到了方法區,并将方法區設計成了永久代。不過,除HotSpot之外的多數虛拟機,并不将方法區當做永
久代,HotSpot本身,也計劃取消永久代。本文中,由于筆者主要使用Oracle JDK6.0,是以仍将使用永久代一詞。
方法區是各個線程共享的區域,用于存儲已經被虛拟機加載的類資訊(即加載類時需要加載的資訊,包括版本、field、方法、接口等資訊)、final常量、靜态變量、編譯器即時編譯的代碼等。
方法區在實體上也不需要是連續的,可以選擇固定大小或可擴充大小,并且方法區比堆還多了一個限制:可以選擇是否執行垃圾收集。一般的,方法區上 執行的垃圾收集是很少的,這也是方法區被稱為永久代的原因之一(HotSpot),但這也不代表着在方法區上完全沒有垃圾收集,其上的垃圾收集主要是針對 常量池的記憶體回收和對已加載類的解除安裝。
在方法區上進行垃圾收集,條件苛刻而且相當困難,效果也不令人滿意,是以一般不做太多考慮,可以留作以後進一步深入研究時使用。
在方法區上定義了OutOfMemoryError:PermGen space異常,在記憶體不足時抛出。
運作時常量池(Runtime Constant Pool)是方法區的一部分,用于存儲編譯期就生成的字面常量、符号引用、翻譯出來的直接引用(符号引用就是編碼是用字元串表示某個變量、接口的位置,直接引用就是根據符号引用翻譯出來的位址,将在類連結階段完成翻譯);運作時常量池除了存儲編譯期常量外,也可以存儲在運作時間産生的常量(比如String類的intern()方法,作用是String維護了一個常量池,如果調用的字元“abc”已經在常量池中,則傳回池中的字元串位址,否則,建立一個常量加入池中,并傳回位址)。
6,直接記憶體(Direct Memory):直接記憶體并不是JVM管理的記憶體,可以這樣了解,直接記憶體,就是 JVM以外的機器記憶體,比如,你有4G的記憶體,JVM占用了1G,則其餘的3G就是直接記憶體,JDK中有一種基于通道(Channel)和緩沖區 (Buffer)的記憶體配置設定方式,将由C語言實作的native函數庫配置設定在直接記憶體中,用存儲在JVM堆中的DirectByteBuffer來引用。 由于直接記憶體收到本機器記憶體的限制,是以也可能出現OutOfMemoryError的異常。
一般來說,一個Java的引用通路涉及到3個記憶體區域:JVM棧,堆,方法區。
以最簡單的本地變量引用:Object obj = new Object()為例:
Object obj表示一個本地引用,存儲在JVM棧的本地變量表中,表示一個reference類型資料;
new Object()作為執行個體對象資料存儲在堆中;
堆中還記錄了Object類的類型資訊(接口、方法、field、對象類型等)的位址,這些位址所執行的資料存儲在方法區中;
在Java虛拟機規範中,對于通過reference類型引用通路具體對象的方式并未做規定,目前主流的實作方式主要有兩種:
1,通過句柄通路(圖來自于《深入了解Java虛拟機:JVM進階特效與最佳實作》):
通過句柄通路的實作方式中,JVM堆中會專門有一塊區域用來作為句柄池,存儲相關句柄所執行的執行個體資料位址(包括在堆中位址和在方法區中的位址)。這種實作方法由于用句柄表示位址,是以十分穩定。
2,通過直接指針通路:(圖來自于《深入了解Java虛拟機:JVM進階特效與最佳實作》)
通過直接指針通路的方式中,reference中存儲的就是對象在堆中的實際位址,在堆中存儲的對象資訊中包含了在方法區中的相應類型資料。這種方法最大的優勢是速度快,在HotSpot虛拟機中用的就是這種方式。
這裡所說的記憶體配置設定,主要指的是在堆上的配置設定,一般的,對象的記憶體配置設定都是在堆上進行,但現代技術也支援将對象拆成标量類型(标量類型即原子類型,表示單個值,可以是基本類型或String等),然後在棧上配置設定,在棧上配置設定的很少見,我們這裡不考慮。
Java記憶體配置設定和回收的機制概括的說,就是:分代配置設定,分代回收。對象将根據存活的時間被分為:年輕代(Young Generation)、年老代(Old Generation)、永久代(Permanent Generation,也就是方法區)。如下圖(來源于《成為JavaGC專家part I》,http://www.importnew.com/1993.html):
年輕代(Young Generation):對象被建立時,記憶體的配置設定首先發生在年輕代(大對象可以直接 被建立在年老代),大部分的對象在建立後很快就不再使用,是以很快變得不可達,于是被年輕代的GC機制清理掉(IBM的研究表明,98%的對象都是很快消 亡的),這個GC機制被稱為Minor GC或叫Young GC。注意,Minor GC并不代表年輕代記憶體不足,它事實上隻表示在Eden區上的GC。
年輕代上的記憶體配置設定是這樣的,年輕代可以分為3個區域:Eden區(伊甸園,亞當和夏娃偷吃禁果生娃娃的地方,用來表示記憶體首次配置設定的區域,再 貼切不過)和兩個存活區(Survivor 0 、Survivor 1)。記憶體配置設定過程為(來源于《成為JavaGC專家part I》,http://www.importnew.com/1993.html):
絕大多數剛建立的對象會被配置設定在Eden區,其中的大多數對象很快就會消亡。Eden區是連續的記憶體空間,是以在其上配置設定記憶體極快;
當Eden區滿的時候,執行Minor GC,将消亡的對象清理掉,并将剩餘的對象複制到一個存活區Survivor0(此時,Survivor1是空白的,兩個Survivor總有一個是空白的);
此後,每次Eden區滿了,就執行一次Minor GC,并将剩餘的對象都添加到Survivor0;
當Survivor0也滿的時候,将其中仍然活着的對象直接複制到Survivor1,以後Eden區執行Minor GC後,就将剩餘的對象添加Survivor1(此時,Survivor0是空白的)。
當兩個存活區切換了幾次(HotSpot虛拟機預設15次,用-XX:MaxTenuringThreshold控制,大于該值進入老年代)之後,仍然存活的對象(其實隻有一小部分,比如,我們自己定義的對象),将被複制到老年代。
從上面的過程可以看出,Eden區是連續的空間,且Survivor總有一個為空。經過一次GC和複制,一個Survivor中儲存着目前還活 着的對象,而Eden區和另一個Survivor區的内容都不再需要了,可以直接清空,到下一次GC時,兩個Survivor的角色再互換。是以,這種方 式配置設定記憶體和清理記憶體的效率都極高,這種垃圾回收的方式就是著名的“停止-複制(Stop-and-copy)”清理法(将Eden區和一個Survivor中仍然存活的對象拷貝到另一個Survivor中),這不代表着停止複制清理法很高效,其實,它也隻在這種情況下高效,如果在老年代采用停止複制,則挺悲劇的。
在Eden區,HotSpot虛拟機使用了兩種技術來加快記憶體配置設定。分别是bump-the-pointer和TLAB(Thread- Local Allocation Buffers),這兩種技術的做法分别是:由于Eden區是連續的,是以bump-the-pointer技術的核心就是跟蹤最後建立的一個對象,在對 象建立時,隻需要檢查最後一個對象後面是否有足夠的記憶體即可,進而大大加快記憶體配置設定速度;而對于TLAB技術是對于多線程而言的,将Eden區分為若幹 段,每個線程使用獨立的一段,避免互相影響。TLAB結合bump-the-pointer技術,将保證每個線程都使用Eden區的一段,并快速的配置設定内
存。
年老代(Old Generation):對象如果在年輕代存活了足夠長的時間而沒有被清理掉(即在幾次 Young GC後存活了下來),則會被複制到年老代,年老代的空間一般比年輕代大,能存放更多的對象,在年老代上發生的GC次數也比年輕代少。當年老代記憶體不足時, 将執行Major GC,也叫 Full GC。
可以使用-XX:+UseAdaptiveSizePolicy開關來控制是否采用動态控制政策,如果動态控制,則動态調整Java堆中各個區域的大小以及進入老年代的年齡。
如果對象比較大(比如長字元串或大數組),Young空間不足,則大對象會直接配置設定到老年代上(大對象可能觸發提前GC,應少用,更應避免使用短命的大對象)。用-XX:PretenureSizeThreshold來控制直接升入老年代的對象大小,大于這個值的對象會直接配置設定在老年代上。
可能存在年老代對象引用新生代對象的情況,如果需要執行Young GC,則可能需要查詢整個老年代以确定是否可以清理回收,這顯然是低效的。解決的方法是,年老代中維護一個512 byte的塊——”card table“,所有老年代對象引用新生代對象的記錄都記錄在這裡。Young GC時,隻要查這裡即可,不用再去查全部老年代,是以性能大大提高。
GC機制的基本算法是:分代收集,這個不用贅述。下面闡述每個分代的收集方法。
年輕代:
事實上,在上一節,已經介紹了新生代的主要垃圾回收方法,在新生代中,使用“停止-複制”算法進行清理,将新生代記憶體分為2部分,1部分 Eden區較大,1部分Survivor比較小,并被劃分為兩個等量的部分。每次進行清理時,将Eden區和一個Survivor中仍然存活的對象拷貝到 另一個Survivor中,然後清理掉Eden和剛才的Survivor。
這裡也可以發現,停止複制算法中,用來複制的兩部分并不總是相等的(傳統的停止複制算法兩部分記憶體相等,但新生代中使用1個大的Eden區和2個小的Survivor區來避免這個問題)
由于絕大部分的對象都是短命的,甚至存活不到Survivor中,是以,Eden區與Survivor的比例較大,HotSpot預設是 8:1,即分别占新生代的80%,10%,10%。如果一次回收中,Survivor+Eden中存活下來的記憶體超過了10%,則需要将一部分對象配置設定到 老年代。用-XX:SurvivorRatio參數來配置Eden區域Survivor區的容量比值,預設是8,代表Eden:Survivor1:Survivor2=8:1:1.
老年代:
老年代存儲的對象比年輕代多得多,而且不乏大對象,對老年代進行記憶體清理時,如果使用停止-複制算法,則相當低效。一般,老年代用的算法是标記-整理算法,即:标記出仍然存活的對象(存在引用的),将所有存活的對象向一端移動,以保證記憶體的連續。
在發生Minor GC時,虛拟機會檢查每次晉升進入老年代的大小是否大于老年代的剩餘空間大小,如果大于,則直接觸發一次Full GC,否則,就檢視是否設 置了-XX:+HandlePromotionFailure(允許擔保失敗),如果允許,則隻會進行MinorGC,此時可以容忍記憶體配置設定失敗;如果不 允許,則仍然進行Full
GC(這代表着如果設定-XX:+Handle PromotionFailure,則觸發MinorGC就會同時觸發Full GC,哪怕老年代還有很多記憶體,是以,最好不要這樣做)。
方法區(永久代):
永久代的回收有兩種:常量池中的常量,無用的類資訊,常量的回收很簡單,沒有引用了就可以被回收。對于無用的類進行回收,必須保證3點:
類的所有執行個體都已經被回收
加載類的ClassLoader已經被回收
類對象的Class對象沒有被引用(即沒有通過反射引用該類的地方)
永久代的回收并不是必須的,可以通過參數來設定是否對類進行回收。HotSpot提供-Xnoclassgc進行控制
使用-verbose,-XX:+TraceClassLoading、-XX:+TraceClassUnLoading可以檢視類加載和解除安裝資訊
-verbose、-XX:+TraceClassLoading可以在Product版HotSpot中使用;
-XX:+TraceClassUnLoading需要fastdebug版HotSpot支援
在GC機制中,起重要作用的是垃圾收集器,垃圾收集器是GC的具體實作,Java虛拟機規範中對于垃圾收集器沒有任何規定,是以不同廠商實作的垃圾 收集器各不相同,HotSpot 1.6版使用的垃圾收集器如下圖(圖來源于《深入了解Java虛拟機:JVM進階特效與最佳實作》,圖中兩個收集器之間有連線,說明它們可以配合使用):
在介紹垃圾收集器之前,需要明确一點,就是在新生代采用的停止複制算法中,“停 止(Stop-the-world)”的意義是在回收記憶體時,需要暫停其他所 有線程的執行。這個是很低效的,現在的各種新生代收集器越來越優化這一點,但仍然隻是将停止的時間變短,并未徹底取消停止。
Serial收集器:新生代收集器,使用停止複制算法,使用一個線程進行GC,其它工作線程暫停。使用-XX:+UseSerialGC可以使用Serial+Serial Old模式運作進行記憶體回收(這也是虛拟機在Client模式下運作的預設值)
ParNew收集器:新生代收集器,使用停止複制算法,Serial收集器的多線程版,用多個線程進行GC,其它工作線程暫停,關注縮短垃圾收集時間。使用-XX:+UseParNewGC開關來控制使用ParNew+Serial Old收集器組合收集記憶體;使用-XX:ParallelGCThreads來設定執行記憶體回收的線程數。
Parallel Scavenge 收集器:新生代收集器,使用停止複制算法,關注CPU吞吐量,即運作使用者代碼的時間/總時間,比如:JVM運作100分鐘,其中運作使用者代碼99分鐘,垃 圾收集1分鐘,則吞吐量是99%,這種收集器能最高效率的利用CPU,适合運作背景運算(關注縮短垃圾收集時間的收集器,如CMS,等待時間很少,是以适 合使用者互動,提高使用者體驗)。使用-XX:+UseParallelGC開關控制使用
Parallel Scavenge+Serial Old收集器組合回收垃圾(這也是在Server模式下的預設值);使用-XX:GCTimeRatio來設定使用者執行時間占總時間的比例,預設99,即 1%的時間用來進行垃圾回收。使用-XX:MaxGCPauseMillis設定GC的最大停頓時間(這個參數隻對Parallel Scavenge有效)
Serial Old收集器:老年代收集器,單線程收集器,使用标記整理(整理的方法是Sweep(清理)和Compact(壓縮),清理是将廢棄的對象幹掉,隻留幸存 的對象,壓縮是将移動對象,将空間填滿保證記憶體分為2塊,一塊全是對象,一塊空閑)算法,使用單線程進行GC,其它工作線程暫停(注意,在老年代中進行标 記整理算法清理,也需要暫停其它線程),在JDK1.5之前,Serial Old收集器與ParallelScavenge搭配使用。
Parallel Old收集器:老年代收集器,多線程,多線程機制與Parallel Scavenge差不錯,使用标記整理(與Serial Old不同,這裡的整理是Summary(彙總)和Compact(壓縮),彙總的意思就是将幸存的對象複制到預先準備好的區域,而不是像Sweep(清 理)那樣清理廢棄的對象)算法,在Parallel Old執行時,仍然需要暫停其它線程。Parallel Old在多核計算中很有用。Parallel
Old出現後(JDK 1.6),與Parallel Scavenge配合有很好的效果,充分展現Parallel Scavenge收集器吞吐量優先的效果。使用-XX:+UseParallelOldGC開關控制使用Parallel Scavenge +Parallel Old組合收集器進行收集。
CMS(Concurrent Mark Sweep)收集器:老年代收集器,緻力于擷取最短回收停頓時間,使用标記清除算法,多線程,優點是并發收集(使用者線程可以和GC線程同時工作),停頓小。使用-XX:+UseConcMarkSweepGC進行ParNew+CMS+Serial
Old進行記憶體回收,優先使用ParNew+CMS(原因見後面),當使用者線程記憶體不足時,采用備用方案Serial Old收集。
CMS收集的方法是:先3次标記,再1次清除,3次标記中前兩次是初始标記和重新标記(此時仍然需要停止(stop the world)), 初始标記(Initial Remark)是标記GC Roots能關聯到的對象(即有引用的對象),停頓時間很短;并發标記(Concurrent remark)是執行GC Roots查找引用的過程,不需要使用者線程停頓;重新标記(Remark)是在初始标記和并發标記期間,有标記變動的那部分仍需要标記,是以加上這一部分 标記的過程,停頓時間比并發标記小得多,但比初始标記稍長。在完成标記之後,就開始并發清除,不需要使用者線程停頓。 是以在CMS清理過程中,隻有初始标記和重新标記需要短暫停頓,并發标記和并發清除都不需要暫停使用者線程,是以效率很高,很适合高互動的場合。 CMS也有缺點,它需要消耗額外的CPU和記憶體資源,在CPU和記憶體資源緊張,CPU較少時,會加重系統負擔(CMS預設啟動線程數為(CPU數量+3)/4)。 另外,在并發收集過程中,使用者線程仍然在運作,仍然産生記憶體垃圾,是以可能産生“浮動垃圾”,本次無法清理,隻能下一次Full GC才清理,是以在GC期間,需要預留足夠的記憶體給使用者線程使用。是以使用CMS的收集器并不是老年代滿了才觸發Full GC,而是在使用了一大半(預設68%,即2/3,使用-XX:CMSInitiatingOccupancyFraction來設定)的時候就要進行Full GC,如果使用者線程消耗記憶體不是特别大,可以适當調高-XX:CMSInitiatingOccupancyFraction以降低GC次數,提高性能,如果預留的使用者線程記憶體不夠,則會觸發Concurrent Mode Failure,此時,将觸發備用方案:使用Serial Old 收集器進行收集,但這樣停頓時間就長了,是以-XX:CMSInitiatingOccupancyFraction不宜設的過大。 還有,CMS采用的是标記清除算法,會導緻記憶體碎片的産生,可以使用-XX:+UseCMSCompactAtFullCollection來設定是否在Full GC之後進行碎片整理,用-XX:CMSFullGCsBeforeCompaction來設定在執行多少次不壓縮的Full GC之後,來一次帶壓縮的Full GC。
G1收集器:在JDK1.7中正式釋出,與現狀的新生代、老年代概念有很大不同,目前使用較少,不做介紹。
注意并發(Concurrent)和并行(Parallel)的差別:
并發是指使用者線程與GC線程同時執行(不一定是并行,可能交替,但總體上是在同時執行的),不需要停頓使用者線程(其實在CMS中使用者線程還是需要停頓的,隻是非常短,GC線程在另一個CPU上執行);
并行收集是指多個GC線程并行工作,但此時使用者線程是暫停的;
是以,Serial和Parallel收集器都是并行的,而CMS收集器是并發的.
關于JVM參數配置和記憶體調優執行個體,見我的下一篇部落格(編寫中:Java系列筆記(4) - JVM監控與調優),本來想寫在同一篇部落格裡的,無奈内容太多,隻好另起一篇。
說明:
本文是Java系列筆記的第3篇,這篇文章寫了很久,主要是Java記憶體和 GC機制相對複雜,難以了解,加上本人這段時間項目和生活中耗費的時間很多,是以進度緩慢。文中大多數筆記内容來源于我在網絡上查到的部落格和《深入了解 Java虛拟機:JVM進階特效與最佳實作》一書。
本人能力有限,如果有錯漏,請留言指正。
參考資料:
《JAVA程式設計思想》,第5章;
《Java深度曆險》,Java垃圾回收機制與引用類型;
《深入了解Java虛拟機:JVM進階特效與最佳實作》,第2-3章;
成為JavaGC專家Part II — 如何監控Java垃圾回收機制, http://www.importnew.com/2057.html
JDK5.0垃圾收集優化之--Don't Pause,http://calvin.iteye.com/blog/91905
【原】java記憶體區域了解-初步了解,http://iamzhongyong.iteye.com/blog/1333100