天天看點

淺談jvm的GC

  GC是什麼?

  GC英文全稱為grabage collection,因為java是運作在jvm上的,而jvm配置設定對象時再堆上配置設定,當jvm想要配置設定對象而堆空間不夠時就會觸發GC。

  哪些是GC對象。

  GC的對象時那些不在被需要的對象,可以稱其為死亡對象。判斷一個對象是否死亡有兩種方法。一種是引用計數法,,每個對象有一個引用計數器,當它被應用就把計數器加一,當取消對它的應用就把計數器減一,當gc是對象的引用計數器值為0時就被判定死亡。這種方法的缺點是無法回收互相應用的對象,比如對象a持有對象b的應用,對象b也持有對象a的應用,而jvm中沒有其他對象對這兩個對象的引用,這兩個對象本該被回收,但因引用計數器不為0而無法被回收。

  還有一種是可達性分析算法。從一些被稱為GC ROOT的對象出發,根據它所持有的引用向下搜尋,搜尋得到的對象就是存活的對象,而周遊所有GC ROOT都無法搜尋的到的對象就是死亡對象。GC ROOT對象有以下這些。

  1. 類常量或者類靜态屬性引用的對象。
  2. 棧中(局部變量表)引用的對象。
  3. JNI(本地方法棧)中引用的對象。

  清理的對象的算法有哪些?

   清理算法有3種。分别是

  标記-清理算法

  先标記那些死去的對象,然後回收它們占有的記憶體。标記清理算法的特點是簡單高效,但是缺點也很明顯,會産生記憶體碎片,可能會導緻配置設定一個對象雖然剩下的記憶體總和足夠但是沒有單個的記憶體碎片滿足要求,就會觸發一次GC。

  複制算法

  複制算法把記憶體分為兩部分,平時隻使用一部分,當GC時把存活的對象移到另一部分,然後清理原來那部分空間,如此循環往複。複制算法解決了記憶體碎片的問題,因為把存活對象複制到另一部分時會把對象整齊的放在一起。但是複制算法隻用到了記憶體的一部分,而存活對象較多時複制算法的效率也比較低,甚至會記憶體不夠容納存活對象的情況,這種問題在後面讨論。

  标記-整理算法

  标記整理算法前面的步驟和标記-清理算法一樣,标記死亡對象,但是後面是把存活對象移動到記憶體的一端,然後清理掉記憶體其他部分。标記整理算法似乎兼并了标記-清理算法和複制算法的優點,既不用專門配置設定一部分空間給存活對象,也不會産生記憶體碎片,但是它的性能還是比複制算法差的。(來自https://www.cnblogs.com/zuoxiaolong/p/jvm5.html,4樓的評論)

   似乎每種算法都有一定的缺點和優點,jvm使用的是哪種算法呢?

  jvm使用的一種分代算法,即把記憶體分為新生代和老年代部分,根據研究,剛創造的對象大部分會在下一次GC中被回收,而一些存活了很久的對象則不太可能會在下一次GC中被回收。是以記憶體被分為新生代和老年代,新生代的對象"朝生夕死",是以很适合用複制算法,新生代80%的區域為Eden,20%被分為兩塊Survivor,10%為From,10%為To。Jvm運作時隻使用Eden區和from區,垃圾回收時将存活對象複制到to區并清理其他區域。然後to區和from區對調,然後接下來繼續使用from區域和eden區。老年代對象又"老而不死",使用标記-整理算法比較合适。而針對新生代的GC被稱為Mirror GC,針對堆中全部對象的GC被稱為Full GC,很明顯MIrror GC比Ful.GC頻繁,而且Full GC比Mirror GC慢10倍以上,是以應該盡量避免Full GC的頻繁發生。

  Mirror GC發生的情況就是當要配置設定記憶體給對象時空間不夠,此時就會發送一個Mirror GC。但并不是所有對象一開始都被配置設定在新生代,jvm中有個參數-XX:PertenureSizeThreshold(以bit為機關),設定了一個值,大小大于這個值的對象會被直接配置設定到老年代,這個參數隻對Serial和ParNew這兩款回收器有用。

  新生代晉升到老年代的一個比較典型的方式就是經過一定次數的GC後仍然存活,每個對象有一個年齡計數器,經過一個MirrorGC後就加一,jvm中用-XX:MaxTenuringThreshold這個參數限定一個對象到多少歲後可以進去老年代。

  動态對象判定年齡

 新生代對象晉升到老年代還有另一種方式,但GC後存活的同年齡對象總和 大于Survivor一半的空間(也就是新生代5%的空間)時,這些對象也會晉升到老年代。

  空間擔保配置設定

每次進行Mirror GC前jvm會檢測老年代的最大連續可用空間是否能容納新生代中的所有對象,如果可以,說明這次Mirror GC是安全的,即使To區域無法容納存活下來的對象,也可以把它們移到老年代。若老年代空間不夠容納存活對象,jvm會檢測曆次晉升到老年代對象的平均大小,如果小于老年代剩餘空間,那jvm會檢測是否允許冒險(由參數HandleePromotionFailure決定,JDK 6 Update 24後不再由該參數決定,jvm一直認為允許冒險)再進行一次Mirror GC。如果不允許冒險或者擔保失敗,那麼jvm還是會進行一次Full GC,若Full GC後空間還是不夠,則jvm會抛出outofmemorty異常。

本文主要參考<<深入了解Java虛拟機,JVM進階特性與最佳實踐>>