第四章 JVM垃圾回收算法


說明:在閱讀本篇之前,需要知道怎么判斷對象的存活與否,見《第三章 JVM內存回收區域+對象存活的判斷+引用類型+垃圾回收線程

注意:本文主要參考自《分布式Java應用:基礎與實踐》,與《深入理解Java虛擬機(第二版)》中的一些說法有一些不同,但是原理一致

 

1、三種垃圾回收算法

  • 標記-清除(年老代)
  • 標記-整理(即標記-壓縮)(年老代)
  • 復制(年輕代)

1.1、標記-清除算法

原理

  • 從根集合節點進行掃描,標記出所有的存活對象,最后掃描整個內存空間並清除沒有標記的對象(即死亡對象)

適用場合

  • 存活對象較多的情況下比較高效
  • 適用於年老代(即舊生代)

缺點

  • 容易產生內存碎片,再來一個比較大的對象時(典型情況:該對象的大小大於空閑表中的每一塊兒大小但是小於其中兩塊兒的和),會提前觸發垃圾回收
  • 掃描了整個空間兩次(第一次:標記存活對象;第二次:清除沒有標記的對象)

注意:

 

1.2、標記整理算法

原理:

  • 從根集合節點進行掃描,標記出所有的存活對象,最后掃描整個內存空間並清除沒有標記的對象(即死亡對象)(可以發現前邊這些就是標記-清除算法的原理),清除完之后,將所有的存活對象左移到一起。

適用場合:

  • 用於年老代(即舊生代)

缺點:

  • 需要移動對象,若對象非常多而且標記回收后的內存非常不完整,可能移動這個動作也會耗費一定時間
  • 掃描了整個空間兩次(第一次:標記存活對象;第二次:清除沒有標記的對象)

優點:

  • 不會產生內存碎片

注意:

 

1.3、復制算法

原理:

  • 從根集合節點進行掃描,標記出所有的存活對象,並將這些存活的對象復制到一塊兒新的內存(圖中下邊的那一塊兒內存)上去,之后將原來的那一塊兒內存(圖中上邊的那一塊兒內存)全部回收掉

適用場合:

  • 存活對象較少的情況下比較高效
  • 掃描了整個空間一次(標記存活對象並復制移動)
  • 適用於年輕代(即新生代):基本上98%的對象是"朝生夕死"的,存活下來的會很少

缺點:

  • 需要一塊兒空的內存空間
  • 需要復制移動對象

注意:

  • 在該情況下,內存規整,對象的內存分配采用"指針碰撞法",見《第二章 JVM內存分配
  • 以空間換時間:通過一塊兒空內存的使用,減少了一次掃描

 

2、垃圾回收機制

根據《第一章 JVM內存結構》所說,年輕代分為Eden區和survivor區(兩塊兒:from和to),且Eden:from:to==8:1:1

1)新產生的對象優先分配在Eden區(除非配置了-XX:PretenureSizeThreshold,大於該值的對象會直接進入年老代);

2)當Eden區滿了或放不下了,這時候其中存活的對象會復制到from區(這里,需要注意的是,如果存活下來的對象from區都放不下,則這些存活下來的對象全部進入年老代),之后Eden區的內存全部回收掉;注意:如果是Eden區沒有滿,但是來了一個小對象Eden區放不下,這時候Eden區存活對象復制到from區后,清空Eden區,之后剛才的小對象再進入Eden區

3)之后產生的對象繼續分配在Eden區,當Eden區又滿了或放不下了,這時候將會把Eden區和from區存活下來的對象復制到to區(同理,如果存活下來的對象to區都放不下,則這些存活下來的對象全部進入年老代),之后回收掉Eden區和from區的所有內存;

4)如上這樣,會有很多對象會被復制很多次(每復制一次,對象的年齡就+1),默認情況下,當對象被復制了15次(這個次數可以通過:-XX:MaxTenuringThreshold來配置),就會進入年老代了

5)當年老代滿了或者存放不下將要進入年老代的存活對象的時候,就會發生一次Full GC(這個是我們最需要減少的,因為耗時很嚴重)

 

總結:

  • 年輕代:復制算法
  • 年老代:標記-清除或標記-整理(前者相較於后者會快一些但是會產生內存碎片,后者相較於前者不會產生內存碎片但是由於要移動存活對象所以會慢一些)
  • 以上這種年輕代與年老代分別采用不同回收算法的方式稱為"分代收集算法",這也是當下企業使用的一種方式
  • 每一種算法都會有很多不同的垃圾回收器去實現,在實際使用中,根據自己的業務特點做出選擇就好

補充:卡表(《實戰java虛擬機》)

 


免責聲明!

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



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