java架構之路-(JVM優化與原理)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 

最進弄了一個公眾號,小菜技術,歡迎大家的加入


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM