1. 程式人生 > >java架構之路-(12)JVM垃圾回收演算法和垃圾回收器

java架構之路-(12)JVM垃圾回收演算法和垃圾回收器

  接上次JVM虛擬機器堆記憶體模型來繼續說,上次我們主要說了什麼時候可能把物件直接放在老年代,還有我們的可能性分析,提出GCroot根的概念。這次我們主要來說說垃圾回收所使用的的演算法和我們的垃圾回收器,需要了解我們的可達性分析GCroot根是什麼,還有我們的動態年齡判斷和老年代分配擔保機制,還不清楚咋回事的小夥伴可以去我上幾篇JVM的部落格去看一下,JVM記憶體模型的幾篇部落格 https://www.cnblogs.com/cxiaocai/p/11520731.html 

  垃圾回收演算法,主要就三種,標記清除,複製,標記整理。

標記清除:

  上圖說話。

 

 

 解釋一下,標記清理演算法,我們可以視為每一個位置去觀察,可以清理就標記,不能清理就放那不動。也可以想象為我們打掃屋子,我們只扔掉我們不想要的物品,其餘想留下的東西還是放在原處不動。

這種演算法清理缺點是效率並不高,而且會帶來大量的空間碎片,我們可以由下面的小方格看到,紅色的格子不是連續的,而是分佈在不同位置的。這時進來一個大物件,需要多個連續的格子,就可能放不下了。

複製:

  不多了直接上圖。

 

 

和上面相比,我們至少可以看到連續的紅格子了,複製演算法就是檢視每一個小格子,可以回收就回收掉,不行就挪到保留的另一半記憶體中去,每次只標記整體區域的一半,我們還是拿收拾屋子舉例,複製演算法就像是,我們把臥室收拾一遍,不要的扔掉,想留下的整齊的放在客廳,下次我們收拾客廳也是如此,每次收拾一半。缺點就是記憶體利用率低,只能使用一半,還需要保留一半的區域為了來回挪動存活物件。

標記整理:

  

 

 

 標記整理是是標記清除的升級版,優點:解決記憶體碎片問題。缺點:整理階段,由於移動了可用物件,需要去更新引用。還是收拾屋子的那個例子,我們把沒用的物品扔掉,其餘物品整齊的擺放起來。

接下來就是和我們回收演算法對應的回收器了。

Serial收集器(-XX:+UseSerialGC -XX:+UseSerialOldGC)

Serial(序列)垃圾收集器是最基本、發展歷史最悠久的收集器;作用於新生代時採用採用複製演算法;Serial Old收集器也就是Serial作用於老年代時採用標記整理演算法。

採用單執行緒手機模式來收集。

 

 

ParNew收集器(-XX:+UseParNewGC) 

ParNew收集器其實就是Serial收集器的多執行緒版本,除了使用多執行緒進行垃圾收集外,其餘行為 (控制引數、收集演算法、回收策略等等)和Serial收集器完全一樣。預設的收集執行緒數跟cpu核數相同,當然也可以用引數(-XX:ParallelGCThreads)指定收集執行緒數,但是一般不推薦修改。 並且單核CPU的伺服器,優先考慮Serial收集器,甚至由於存線上程互動的開銷,效果不一定強於Serial收集器,可能Serial收集器效果更好,parNew新生代採用複製演算法,老年代採用標記-整理演算法。 建議使用在新生代,後面會說為什麼。

 

 

 

Parallel Scavenge收集器(-XX:+UseParallelGC,- XX:+UseParallelOldGC)

 Parallel Scavenge 收集器類似於ParNew 收集器,是Server 模式(記憶體大於2G,2個cpu)下的預設收集器, Parallel Scavenge更加關注於CPU的使用率,可能在回收的過程瞬間CPU使用率提高進行垃圾回收。Parallel Old收集器是Parallel Scavenge收集器的老年代版本。和上面的圖一樣 ,我就不再貼圖了。新生代採用複製演算法,老年代採用標記-整理演算法。 

CMS收集器(-XX:+UseConcMarkSweepGC(old)) 

  相對上面幾個收集器來說稍微複雜一些,先看圖。

 由圖來看確實複雜了不少,但是並不是一直處於STW階段,中間還有並行的時候。我們來看一下每一個階段都是做什麼的。

初始化標記階段:

  這裡STW時間非常短,微乎其微的,這裡只標記GCRoot根直接引用的物件,不往下層去搜索更多的物件進行標記,效率很高,STW時間很短。

併發標記階段:

  併發標記階段是最消耗時間的,在上一個階段只標記GCRoot根的直接引用物件,這個階段是把所有需要回收的物件都需要標記出來。可能引用很多,所以耗時較大。但是還好,他不會出現STW,不會停掉所有執行緒為其單一服務。

重新標記階段:

  在上一個相對比龐大的併發標記階段,並沒有STW,可能會產生新的GCRoot根,或者說原有不需要回收的物件現在已經變為垃圾物件了,我們在重新標記階段再一次來做一下處理,這裡又會出現STW現象,相比並發標記階段時間也是很短的。

併發清理階段:

  恢復我們的正常的執行緒,開始清理沒有標記的物件,這裡不會產生STW,在這個階段再進來的新物件,或者產生物件的變更,CMS是不會繼續處理的,會在下一次垃圾回收再來處理這些物件。

併發重置階段:

  清楚所有標記,為下一次垃圾回收做準備。

CMS垃圾收集器步驟比較多,但是我們可以看出明顯提高了效率,中間至少不是持續的STW的。但是也有CMS的弊端的,併發標記階段和併發清理階段很容易和我們正常的執行緒搶佔CPU的。再就是他的演算法只是標記清理,並沒有整理記憶體碎片,但可以調配引數做到整理碎片的目的。最坑的問題來了,就是我們在併發標記階段,可能進來新的物件,本來我們老年代就快滿了,才進行的垃圾收回,這時這些物件過大過多,會再次執行CMS的垃圾回收,造成concurrent mode failure,這時會變更為Serial收集器,產生較長的STW時間。

CMS相關引數:

1. -XX:+UseConcMarkSweepGC:啟用cms垃圾回收器
2. -XX:ConcGCThreads:併發的GC執行緒數目
3. -XX:+UseCMSCompactAtFullCollection:FullGC之後做壓縮整理(減少碎片)
4. -XX:CMSFullGCsBeforeCompaction:多少次FullGC之後壓縮一次,預設是0,代表每次 FullGC後都會壓縮一次,依賴上面的引數,必須配置-XX:+UseCMSCompactAtFullCollection才生效
5. -XX:CMSInitiatingOccupancyFraction: 當老年代使用達到該比例時會觸發FullGC(預設 是92,這是百分比),可能出現JVM自我優化變更的現象,不是很穩定
6. -XX:+UseCMSInitiatingOccupancyOnly:只使用設定的回收閾值(-XX:CMSInitiatingOccupancyFraction設定的值),如果不指定,JVM僅在第一次使用設定值,後續則會自動調整,比上面的-XX:CMSInitiatingOccupancyFraction穩定很多
7. -XX:+CMSScavengeBeforeRemark:在CMS GC前啟動一次minor gc,目的在於減少 老年代對年輕代的引用,降低CMS GC的標記階段時的開銷,一般CMS的GC耗時 80%都在重新標記階段 

 

今天先說這麼多 ,還有一個G1收集器沒有說,下次部落格會說G1收集器,和常見的調優方式,有時間我再整理一份常用的命令。

看起來真的吃力的話,建議先看一下我前幾篇JVM相關的部落格,JVM記憶體模型的幾篇部落格 https://www.cnblogs.com/cxiaocai/p/11520731.html