JVM(5)——垃圾回收器

四 垃圾回收器

1JVM的年輕代垃圾回收器ParNew是如何工作的?

最常用的新生代垃圾回收器:ParNew

新生代的ParNew垃圾回收器主打的就是多線程垃圾回收機制,另外一種Serial垃圾回收器主打的是單線程垃圾回收,他們倆都是回收新生代的,唯一的區別就是單線程和多線程的區別,但是垃圾回收算法是完全一樣的。

ParNew垃圾回收器如果一旦在合適的時機執行Minor GC的時候,就會把系統程序的工作線程全部停掉,禁止程序繼續運行創建新的對象,然後自己就用多個垃圾回收線程去進行垃圾回收

2如何爲線上系統指定使用ParNew垃圾回收器?

使用“-XX:+UseParNewGC”選項,只要加入這個選項,JVM啓動之後對新生代進行垃圾回收的,就是ParNew垃圾回收器了。

3ParNew垃圾回收器默認情況下的線程數量

指定了使用ParNew垃圾回收器之後,他默認給自己設置的垃圾回收線程的數量就是跟CPU的核數是一樣的

比如我們線上機器假設用的是4核CPU,或者8核CPU,或者16核CPU,那麼此時ParNew的垃圾回收線程數就會分別是4個線程、8個線程、16個線程

4CMS垃圾回收的基本原理

老年代我們選擇的垃圾回收器是CMS,CMS採取的“標記-清理”算法。這種方法其實最大的問題,就是會造成很多內存碎片。

CMS垃圾回收器採取的是垃圾回收線程和系統工作線程儘量同時執行的模式來處理的

5CMS如何實現系統一邊工作的同時進行垃圾回收?

CMS在執行一次垃圾回收的過程一共分爲4個階段:

  1. 初始標記
    標記出來所有GC Roots直接引用的對象,要造成“Stop the World”暫停一切工作線程,但是其實影響不大,因爲他的速度很快,僅僅標記GC Roots直接引用的那些對象罷了。
  2. 併發標記
    這個階段會讓系統線程可以隨意創建各種新對象,繼續運行,在運行期間可能會創建新的存活對象,也可能會讓部分存活對象失去引用,變成垃圾對象。第二個階段,就是對老年代所有對象進行GC Roots追蹤,其實是最耗時的
  3. 重新標記
    重新標記下在第二階段裏新創建的一些對象,還有一些已有對象可能失去引用變成垃圾的情況,這個重新標記的階段,是速度很快的,他其實就是對在第二階段中被系統程序運行變動過的少數對象進行標記,所以運行速度很快。接着重新恢復系統程序的運行,
  4. 併發清理
    這個階段就是讓系統程序隨意運行,然後他來清理掉之前標記爲垃圾的對象即可。

第二階段和第四階段,都是和系統程序併發執行的,所以基本這兩個最耗時的階段對性能影響不大。

只有 第一個階段和第三個階段是需要“Stop the World”的,但是這兩個階段都是簡單的標記而已,速度非常的快,所以基本上對系統運行響應也不大。

6 CMS垃圾回收器缺陷

  1. 第一個問題就是會消耗CPU資源,在併發標記和併發清理兩個最耗時的階段,垃圾回收線程和系統工作線程同時工作,會導致有限的CPU資源被垃圾回收線程佔用了一部分。但是因爲老年代裏存活對象是比較多的,這個過程會追蹤大量的對象,所以耗時較高。併發清理,又需要把垃圾對象從各種隨機的內存位置清理掉,也是比較耗時的。所以在這兩個階段,CMS的垃圾回收線程是比較耗費CPU資源的。CMS默認啓動的垃圾回收線程的數量是(CPU核數 + 3)/ 4。

  2. 第2個問題就是採用標記清楚會導致垃圾碎片。

7 Concurrent Mode Failure問題

​ 在併發清理階段,CMS是回收之前標記好的垃圾對象,但是系統一直在運行,可能會隨着系統運行讓一些對象進入老年代,同時還變成垃圾對象,這種垃圾對象是“浮動垃圾”。但是CMS只能回收之前標記出來的垃圾對象,不會回收他們,需要等到下一次GC的時候纔會回收他們。所以爲了保證在CMS垃圾回收期間,還有一定的內存空間讓一些對象可以進入老年代,一般會預留一些空間。CMS垃圾回收的觸發時機,其中有一個就是當老年代內存佔用達到一定比例了,就自動執行GC。“-XX:CMSInitiatingOccupancyFaction”參數可以用來設置老年代佔用多少比例的時候觸發CMS垃圾回收,JDK 1.6裏面默認的值是92%。

那麼如果CMS垃圾回收期間,系統程序要放入老年代的對象大於了可用內存空間,此時會如何?

​ 會發生Concurrent Mode Failure,就是說併發垃圾回收失敗了,我一邊回收,你一邊把對象放入老年代,內存都不夠了。此時就會自動用“Serial Old”垃圾回收器替代CMS,強行把系統程序“Stop the World”,重新進行長時間的GC Roots追蹤,標記出來全部垃圾對象,不允許新的對象產生,然後一次性把垃圾對象都回收掉,完事兒了再恢復系統線程。

所以在生產實踐中,這個自動觸發CMS垃圾回收的比例需要合理優化一下,避免“Concurrent Mode Failure”問題

8內存碎片問題

CMS採用“標記-清理”算法,每次都是標記出來垃圾對象,然後一次性回收掉,這樣會導致大量的內存碎片產生。如果內存碎片太多,會導致後續對象進入老年代找不到可用的連續內存空間了,然後觸發Full GC。

CMS有一個參數是“-XX:+UseCMSCompactAtFullCollection”,默認就打開了,意思是在Full GC之後要再次進行“Stop the World”,停止工作線程,然後進行碎片整理,就是把存活對象挪到一起,空出來大片連續內存空間,避免內存碎片

還有一個參數是“-XX:CMSFullGCsBeforeCompaction”,這個意思是執行多少次Full GC之後再執行一次內存碎片整理的工作,默認是0,意思就是每次Full GC之後都會進行一次內存整理。

9爲啥老年代的Full GC要比新生代的Minor GC慢很多倍,一般在10倍以上?

  1. 在併發標記階段,他需要去追蹤所有存活對象,老年代存活對象很多,這個過程就會很慢,
  2. 其次併發清理階段,他不是一次性回收一大片內存,而是找到零零散散在各個地方的垃圾對象,速度也很慢;
  3. 最後還得執行一次內存碎片整理,把大量的存活對象給挪在一起,空出來連續內存空間,這個過程還得“Stop the World”,那就更慢了。

萬一併發清理期間,剩餘內存空間不足以存放要進入老年代的對象了,引發了“Concurrent Mode Failure”問題,那更是麻煩,還得立馬用“Serial Old”垃圾回收器,“Stop the World”之後慢慢重新來一遍回收的過程,這更是耗時了。

10幾個觸發老年代GC的時機嗎?

第一是老年代可用內存小於新生代全部對象的大小,如果沒開啓空間擔保參數,會直接觸發Full GC,所以一般空間擔保參數都會打開

第二是老年代可用內存小於歷次新生代GC後進入老年代的平均對象大小,此時會提前Full GC;

第三是新生代Minor GC後的存活對象大於Survivor,那麼就會進入老年代,此時老年代內存不足。

第四就是“-XX:CMSInitiatingOccupancyFaction”參數,如果老年代可用內存大於歷次新生代GC後進入老年代的對象平均大小,但是老年代已經使用的內存空間超過了這個參數指定的比例,也會自動觸發Full GC。

11內存到底該如何分配?

  1. 新生代垃圾回收優化之一:Survivor空間夠不夠
    避免Minor GC後對象放不下Survivor進入老年代,或者是動態年齡判定之後進入老年代,給新生代裏的Survivor充足的空間,那麼Minor GC一般就沒什麼問題。
  2. 新生代對象躲過多少次垃圾回收後進入老年代?
  3. 多大的對象直接進入老年代?
  4. 指定垃圾回收器
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章