上節我們介紹了JVM垃圾回收的原則,還有幾個垃圾收集演算法:標記-清除演算法、複製演算法、標記整理演算法、分代收集演算法;現在將要說HotSpt的垃圾收集器,這小節將只是理論。
Java虛擬機器規範對垃圾收集器的具體實現並沒有任何規定,所以不同廠商、不同版本的虛擬機器提供的垃圾收集器會有很大的不同。下面所介紹的收集器只是HotSpt1.7的垃圾收集器。
HotSpot堆的瓜分
HotSpt它把記憶體空間分為幾個區域:新生代、老年代、永久代;上節中也說到了分代垃圾收集演算法的主要思想按物件的生命週期來進行分組;
如上圖JVM把堆劃分為Young、Old、Perm三大區域,對應著不能年齡的物件;然後又把Young分為:Eden、Survivor From、Survivor To三小塊;各個區域存放物件的區別如下:
- Young區,所有新建立的物件都儲存在Eden區域中,當Eden滿後將會觸發minor GC把Eden中存活的物件複製到一個Survivor中,然後另一個Survivor存活物件也複製到這個中,始終保持一個Survivor區域為空的。
- Old區存放的是Survivor滿後觸發minor GC後依然存活的物件,如果從Eden複製到Survivor的物件存不了也可以直接存到Old區中等。Old區滿了就會觸發Full GC回收整個堆記憶體。
- Perm區儲存類、方法、等的元資訊,Perm也會觸發Full GC進行垃圾回收。
Sun有對各個區域給出建議的大小,Young區域為整個堆的1/4,Young中的Survivor為Young區域的1/8,安裝JDK的時候有自帶了visualvm工具,可以安裝Visual GC外掛來檢視到JVM各個區域的垃圾回收情況,可以看到記憶體大小、GC時間、已使用大小、剩餘大小等等資訊如圖:
垃圾收集器
HotSpot提供了七種類垃圾收集器:Serial 收集器、ParNew 收集器、Parallel Scavenge收集器、Serial Old收集器、Parallel Old 收集器、CMS 收集器、G1 收集器。
Serial 收集器是比較古老的一種收集器,不過到現在他還是JVM client模式中預設新生代的GC演算法, Serial是單執行緒的,它在進行垃圾回收工作時會暫停所有其他工作,Sum稱為:“Stop The World”,直到回收任務結束,然後Serial工作時佔用的時間比較多但它比較簡單新生代記憶體不大的情況下回收工作時間還是短到可以接收的,基本一秒以內。
ParNew 收集器與Serial Collector唯一不同的就是Serial Collector是單執行緒的,ParNew Collector是多執行緒的,ParNew Collector是JVM Server模式中預設的新生代GC演算法。
Parallel Scavenge收集器 也是新生代收集演算法,使用複製演算法、並行的多執行緒,它的優勢在於提高吞吐量,GC停頓的時間越短吞吐量就會越高,
Serial Old收集器為Serial的老年代版本,單執行緒、“標記-整理”演算法,在Client模式下為虛擬機器使用。
Parallel Old 收集器為Parallel Scavenge的老年代版本,使用多執行緒、“標記-清除演算法”,
CMS(Concurrent Mark Sweep)收集器是以最短停頓時間為目標的收集器,使用了“標記清除”演算法實現,不過這個收集器比前面幾個收集器都要複雜,運作過程有這麼幾個步驟:
1、 初始標記(CMS initial mark)
2、 併發標記(CMS concurrent mark)
3、 重新標記(CMS remark)
4、 併發清除(CMS concurrent sweep)
在初始標記、重新標記的時候會“Stop The World”,初始標記標記出GC Roots能直接關聯到的物件,併發標記進行GC Roots Tracing,重新標記修復在程式繼續執行導致標記的變動,CMS也有不好的地方就是CMS會佔用較多的CPU由於CMS是使用標記清除演算法實現的,所以可能會導致較多的碎片。
G1收集器JDK1.7釋出的時候才退出的演算法,可以說是比較新的技術,相比CMS G1不會產生碎片,因為他使用的是“標記-整理”演算法,G1還有就是能比較精確的空間停頓時間可以在不犧牲吞吐量的情況下進行垃圾回收,G1把整個Java堆(新生代、老年代)瓜分為多個獨立區域,跟蹤這些區域的垃圾堆積程度,然後維護一個優先列表,根據允許的時間優先回收垃圾最多的區域。
記憶體分配策略
記憶體的分配規則不是百分之百固定的,它取決與虛擬機器的相關引數配置還有使用的垃圾收集器組合。這裡說的只是最普遍的記憶體分配規則,這裡只是說些理論,在下篇文章將會用程式碼去驗證。
Eden優先分配
絕大多數情況下物件建立的時候在Eden區域分配,當分配的時候Eden中空間不足時將觸發Minor GC,
較大物件直接進入老年代
較大物件:需要使用大量連續的記憶體空間的Java物件,常見的有:很長的字串、陣列,寫程式碼的時候能避免儘量避免短命較大物件,因為大物件常常會引發GC。
生命週期較長的物件進入老年代
分代垃圾收集的思想就是按物件的生命週期來分開管理,虛擬機器為每個物件定義了一個物件年齡計數器,物件在Eden區中經過一次Minor GC後仍存活並複製到Survivor中,物件年齡就為1,物件每在Survivor中度過一次Minor GC年齡將加1,當年齡到一定值(預設15)的時候物件將晉升到老年代。閥值可以通過引數設定,後面將介紹到。
動態年齡判定
虛擬機器並不定死說必須達到MaxTenuringThreshold年齡才能晉升老年代;如在Survivor空間中相同年齡所有物件大小總和大於Survivor空間的一半,大於或等於該年齡的物件就可以直接進入老年代中,無需等到指定的年齡。
空間擔保分配
年輕代在發生Minor GC時,虛擬機器會檢測每次晉升到老年代的平均大小是否大於老年代剩餘的儲存空間,比老年代剩餘空間大則改為直接Full GC,如果小,則看HandlePromotionFailure設定是否允許擔保失敗,是則只會進行Minor GC,否則也要改為進行一次Full GC。
文章首發地址:Solinx
http://www.solinx.co/archives/58