一、基本回收演算法
1. 引用計數(Reference Counting)
比較古老的回收演算法。原理是此物件有一個引用,就是增加一個計數,刪除一個引用則減少一個計數。垃圾回收時,只用收集數為0的物件。此演算法最致命的是無法處理循環引用的問題。
2. 標記-清除(Mark-Sweep)
此演算法執行分兩階段。第一階段從引用根節點開始標記所有被引用的對象,第二階段遍歷整個堆,把未標記的物件清除。此演算法需要暫停整個應用,同時,會產生記憶體碎片。
3. 複製(Copying)
此演算法將記憶體空間劃為兩個相等的區域,每次只使用其中一個區域。垃圾回收時,遍歷目前使用區域,把正在使用中的物件複製到另一個區域。次演算法每次只處理正在使用中的對象,因此複製成本比較小,同時複製過去以後還能進行對應的記憶體整理,不過出現「碎片」問題。當然,此演算法的缺點也是很明顯的,就是需要兩倍記憶體空間。
4. 標記-整理(Mark-Compact)
此演算法結合了 「標記-清除」和「複製」兩個演算法的優點。也是分兩階段,第一階段從根節點開始標記所有被引用對象,第二階段遍歷整個堆,把清除未標記對象並且把存活對象 “壓縮”到堆的其中一塊,按順序排放。此演算法避免了「標記-清除」的碎片問題,同時也避免了「複製」演算法的空間問題。
5. 增量收集(Incremental Collecting)
實作垃圾回收演算法,即:在應用進行的同時進行垃圾回收。不知道什麼原因JDK5.0中的收集器沒有使用這種演算法的。
6. 分代(Generational Collecting)
是基於物件生命週期分析後所得的垃圾回收演算法。把物件分為年青代、年老代、持久代,對不同生命週期的物件使用不同的演算法(上述方式中的一個)進行回收。現在的垃圾回收器(從J2SE1.2開始)都是使用此演算法的。
1. Young(年輕代)
年輕代分三個區。一個Eden區,兩個 Survivor區。大部分物件在Eden區中產生。當Eden區滿時,還存活的物件將複製到Survivor區(兩個中的一個),當這個Survivor區滿時,此區的存活物件將複製到另外一個Survivor區,當這個Survivor去也滿了的時候,從第一個Survivor區複製過來的並且此時還存活的對象,將被複製「年老區(Tenured)」。需要注意,Survivor的兩個區是對稱的,沒先後關係,所以同一個區中可能同時存在從Eden複製過來對象,和從前一個Survivor複製過來的對象,而復製到年老區的只有從第一個Survivor去過來的物件。而且,Survivor區總有一個是空的。
2. Tenured(年老代)
年老代存放從年輕代存活的對象。一般來說年老代存放的都是生命期較長的對象。
3. Perm(持久代)
用來存放靜態文件,如今Java類別、方法等。持久性對垃圾回收沒有顯著影響,但是有些應用可能動態產生或呼叫一些class,例如Hibernate等,在這種時候需要設定一個比較大的持久代空間來存放這些運行過程中新增的類別。持久代大小透過-XX:MaxPermSize=
二、GC型
GC有兩種:Scavenge GC和Full GC。
1. Scavenge GC
一般情況下,當新對像生成,並且在Eden申請空間失敗時,就好觸發Scavenge GC,堆Eden區域進行GC,清除非存活對象,並且把尚且存活的對象移動到Survivor區。然後整理Survivor的兩個區。
2. Full GC
對整堆進行整理,包括Young、Tenured和Perm。 Full GC比Scavenge GC慢,因此應該盡可能減少Full GC。有如下原因可能導致Full GC:
* Tenured被寫滿
* Perm域被寫滿
* System.gc()被顯示調用
* 上一次GC之後Heap的各域分配策略動態變化
分代垃圾回收流程示範
1.
2.
3.
4.
二、垃圾回收器
目前的收集器主要有三種:序列收集器、並行收集器、並發收集器。
1. 串列收集器
使用單執行緒處理所有垃圾回收工作,因為無需多執行緒交互,所以效率比較高。但是,也無法使用多處理器的優勢,所以此收集器適合單處理器機器。當然,此收集器也可以用在小資料量(100M左右)情況下的多處理器機器上。可以使用-XX:+UseSerialGC開啟。
2. 平行收集器
1. 對年輕代進行並行垃圾回收,因此可以減少垃圾回收時間。一般在多執行緒多處理器機器上使用。使用-XX:+UseParallelGC.開啟。並行收集器在J2SE5.0第六6更新上引入,在Java SE6.0中進行了增強--可以堆年老代進行並行收集。如果年老代不使用並發收集的話,是使用單線程進行垃圾回收,因此會限制擴展能力。使用-XX:+UseParallelOldGC開啟。
2. 使用-XX:ParallelGCThreads=
3. 此收集器可以進行如下配置:
* 最大垃圾回收暫停:指定垃圾回收時的最長暫停時間,透過-XX:MaxGCPauseMillis=
* 吞吐量:吞吐量為垃圾回收時間與非垃圾回收時間的比值,透過-XX:GCTimeRatio=
3. 並發收集器
可以確保大部分工作都並發進行(應用不停止),垃圾回收只暫停很少的時間,此收集器適合對響應時間要求比較高的中、大規模應用。使用-XX:+UseConcMarkSweepGC開啟。
1. 並發收集器主要減少年老代的暫停時間,他在應用不停止的情況下使用獨立的垃圾回收線程,追蹤可達物件。在每個年老代垃圾回收週期中,在收集初期並發收集器會對整個應用進行簡短的暫停,在收集中還會再暫停一次。第二次暫停會比第一次稍長,在此過程中多個執行緒同時進行垃圾回收工作。
2. 並發收集器使用處理器換來短暫的停頓時間。在一個N個處理器的系統上,並發收集部分使用K/N個可用處理器進行回收,一般情況下13. 在只有一個處理器的主機上使用並發收集器,設定為incremental mode模式也可獲得較短的停頓時間。
4. 浮動垃圾:由於在應用運行的同時進行垃圾回收,所以有些垃圾可能在垃圾回收進行完成時產生,這樣就造成了“Floating Garbage”,這些垃圾需要在下次垃圾回收週期時才能回收掉。所以,並發收集器一般需要20%的預留空間用於這些浮動垃圾。
5. Concurrent Mode Failure:並發收集器在應用運行時進行收集,所以需要確保堆在垃圾回收的這段時間有足夠的空間供程序使用,否則,垃圾回收還未完成,堆空間先滿了。這種情況下將會發生“並發模式失敗”,此時整個應用程式將會暫停,進行垃圾回收。
6. 啟動並發收集器:因為並發收集在應用運行時進行收集,所以必須確保收集完成之前有足夠的記憶體空間供程式使用,否則會出現「Concurrent Mode Failure」。透過設定-XX:CMSInitiatingOccupancyFraction=
4. 小結
* 序列處理器:
--適用情況:資料量比較小(100M左右);單處理器下並且對響應時間無要求的應用。
--缺點:只能用於小型應用
* 平行處理器:
--適用情況:“對吞吐量有高要求”,多CPU、對應用響應時間無要求的中、大型應用。舉例:後台處理、科學計算。
--缺點:應用回應時間可能較長
* 並發處理器:
--適用情況:“對回應時間有高要求”,多CPU、對應用回應時間有較高要求的中、大型應用。舉例:Web伺服器/應用伺服器、電信交換、整合開發環境。
三、GC基本原理
GC(Garbage Collection),是JAVA/.NET中的垃圾收集器。
Java是由C++發展來的,它擯棄了C++中一些繁瑣容易出錯的東西,引入了計數器的概念,其中有一條就是這個GC機制(C#借鑒了JAVA)
程式設計人員容易出現問題的地方,忘記或者錯誤的記憶體回收會導致程式或系統的不穩定甚至崩潰,Java提供的GC功能可以自動監測物件是否超過作用域從而達到自動回收記憶體的目的,Java語言沒有提供釋放已分配記憶體的顯示操作方法。所以,Java的記憶體管理其實就是物件的管理,其中包括物件的分配和釋放。
對於程式設計師來說,分配對象使用new關鍵字;釋放對象時,只要將對象所有引用賦值為null,讓程式不能夠再訪問到這個對象,我們稱該對象為"不可達的".GC將負責回收所有"不可達"物件的記憶體空間。
對於GC來說,當程式設計師建立物件時,GC就開始監控這個物件的位址、大小、使用情況。通常,GC會採用有向圖的方式來記錄和管理堆(heap)中的所有物件。以這種方式確定哪些物件是"可達的",哪些物件是"不可達的".當GC確定一些物件為"不可達"時,GC就有責任回收這些記憶體空間。但是,為了確保 GC能夠在不同平台實現的問題,Java規範對GC的許多行為都沒有嚴格的規定。例如,對於採用哪種類型的回收演算法、何時進行回收等重要問題都沒有明確的規定。因此,不同的JVM的實現者往往有不同的實作演算法。這也為Java程式設計師的開發帶來行多不確定性。本文研究了幾個與GC工作相關的問題,努力減少這種不確定性對Java程式帶來的負面影響。
四、GC分代劃分
JVM記憶體模型中Heap區分兩大塊,一塊是Young Generation,另一塊是Old Generation
1) 在Young Generation中,有一個叫EdenSpace 的空間,主要是用來存放新生的對象,還有兩個Survivor Spaces(from、to),它們的大小總是一樣,它們用來存放每次垃圾回收後存活下來的對象。
2) 在Old Generation中,主要存放應用程式中生命週期長的記憶體物件。
3) 在Young Generation區塊中,垃圾回收一般用Copying的演算法,速度快。每次GC的時候,存活下來的物件首先由Eden拷貝到某個SurvivorSpace,當Survivor Space空間滿了後,剩下的live物件就被直接拷貝到OldGeneration中去。因此,每次GC後,Eden記憶體區塊會被清空。
4) 在Old Generation區塊中,垃圾回收一般用mark-compact的演算法,速度慢些,但減少記憶體需求。
5) 垃圾回收分多級,0級為全部(Full)的垃圾回收,會回收OLD段中的垃圾;1級或以上為部分垃圾回收,只會回收Young中的垃圾,內存溢出通常發生於OLD段或Perm段垃圾回收後,仍然無記憶體空間容納新的Java物件的情況。
五、增量式GC
增量式GC(Incremental GC),是GC在JVM中通常是由一個或一組進程來實現的,它本身也和用戶程式一樣佔用heap空間,運行時也佔用CPU 。
當GC進程運作時,應用程式停止運作。因此,當GC運行時間較長時,使用者能夠感覺到Java程式的停頓,另外一方面,如果GC運行時間太短,則可能物件回收率太低,這意味著還有很多應該回收的物件沒有被回收,仍然佔用大量記憶體。因此,在設計GC的時候,就必須在停頓時間和回收率之間進行權衡。一個好的GC實現允許用戶定義自己所需的設置,例如有些內存有限的設備,對內存的使用量非常敏感,希望GC能夠準確的回收內存,它並不關心程序速度的快慢。另外一些即時網路遊戲,就無法允許程式有長時間的中斷。
增量式GC就是透過一定的回收演算法,把一個長時間的中斷,分成很多小的中斷,透過這種方式減少GC對使用者程式的影響。雖然,增量式GC在整體效能上可能不如普通GC的效率高,但它能夠減少程式最長的停頓時間。
Sun JDK提供的HotSpot JVM就能支援增量式GC。 HotSpot JVM缺省GC方式為不使用增量GC,為了啟動增量GC,我們必須在執行Java程式時增加-Xincgc的參數。
HotSpot JVM增量式GC的實現是採用Train GC演算法,它的基本想法就是:將堆中的所有物件按照創建和使用情況進行分組(分層),將使用頻繁高和具有相關性的物件放在一隊中,隨著程式的運行,不斷對組進行調整。當GC運作時,它總是先回收最老的(最近很少訪問的)的對象,如果整組都是可回收對象,GC將整組回收。這樣,每次GC運作只回收一定比例的不可達對象,確保程式的順暢運作。
更多淺談關於Java的GC垃圾回收器的一些基本概念相關文章請關注PHP中文網!