1. 程式人生 > >Java記憶體分配策略與垃圾收集器

Java記憶體分配策略與垃圾收集器

判斷物件是否死亡的方法

1)引用計數演算法 給物件新增一個引用計數器,每當一個地方引用它時,計數器加1,當引用失效,計數器減1,任何時刻計數器為0的物件就是不可能再被使用。然而主流的Java虛擬機器裡面沒有選用引用計數演算法來管理記憶體,因為無法解決物件之間相互迴圈引用的問題。 2)可達性分析演算法 以一系列稱為“GC Roots”的物件作為起始點,從這些節點開始向下搜尋,搜尋走過的路徑稱為引用鏈,當一個物件到GC Roots沒有任何引用鏈相連時,此物件不可用。 可作為GC Roots的物件如下:

  • 虛擬機器棧(棧幀中的本地變量表)中的引用物件
  • 方法區中類靜態屬性引用的物件
  • 方法區中引用常量的物件
  • 本地方法棧中JNI(一般說Native方法)引用的物件

強引用、軟引用、弱引用、虛引用

1)強引用:類似“Object obj = new Object()”這類引用,只要強引用還在,GC就不會回收物件。 2)軟引用:用來描述一些還有用但非必需的物件。軟引用關聯的物件,在系統將要發生記憶體溢位異常之前,將會把這些物件列進回收範圍之中進行第二次回收。 3)弱引用:用來描述非必需的物件。被弱引用關聯的物件只能生存到下一次GC之前,GC時無論當前記憶體是否足夠,都會回收掉只被弱引用關聯的物件。 4)虛引用:虛引用唯一的目的就是能在這個物件被收集器回收時收到一個系統通知。

回收方法區

永久代回收兩部分內容:廢棄常量和無用的類。 1)回收廢棄常量與回收Java堆中的物件類似,當沒地方引用這個字面量且有必要發生記憶體回收的,則回收這個字面量。 2)回收無用的類需要同時滿足3個條件:

  • 該類所有的例項都已經被回收,Java堆中不存在該類的任何例項。
  • 載入該類的ClassLoader已經被回收。
  • 該類對應的java.lang.Class物件沒有再任何地方被引用,無法在任何地方通過反射訪問該類的方法。

垃圾收集演算法

標記-清除演算法

具體過程不講,有兩個缺點: 1)效率問題,標記和清除兩個過程效率都不高。 2)標記清除之後會產生大量不連續的記憶體碎片,碎片太多可能會導致以後在程式執行過程中需要分配較大物件時,無法找到足夠的連續記憶體而不得不提前觸發另一次垃圾收集動作。

複製演算法(新生代演算法)

將記憶體容量劃分為大小相等的兩塊,每次只用一塊,當這一塊記憶體用完了,就將還存活

的物件複製到另一塊上,然後把已使用的記憶體空間一次清理掉。缺點是把記憶體縮小為原來的一半,代價太高。 所以這種演算法用來回收新生代,由於新生代的物件98%是“朝生夕死”的,所以記憶體比例不用按1:1劃分,通常劃分為一塊較大的Eden空間和兩塊較小的Survivor空間,每次使用Eden區和一塊Survivor,回收時,將Eden區和Survivor區中還存活的物件一次性複製到另外一塊Survivor上。當Survivor空間不夠時,需要依賴其他記憶體進行擔保分配。

標記-整理演算法(老年代演算法)

老年代的物件存活率較高,複製操作效率會變低,可能需要應對物件100%存活的情況,所以不適用複製演算法。所以提出了“標記-整理”演算法。 “標記-整理”演算法過程與“標記-清除”演算法一致,但後續步驟不是直接對可回收物件進行清理,而是讓所以存活物件都向一段移動,然後直接清理掉端邊界以外的記憶體。

垃圾收集器

CMS收集器(老年代收集器)

CMS收集器以獲取最短回收停頓時間為目標,基於“標記-清除”演算法,分初始標記、併發標記、重新標記、併發清除四個階段。

  • 初始標記、重新標記需要stop the world。
  • 初始標記僅僅是標記下GC Roots能直接關聯到的物件,速度很快。
  • 併發標記就是進行GC Roots Tracing的過程。
  • 重新標記是為了修正併發標記期間因使用者程式繼續執行而導致標記產生變動的那一部分物件的標記記錄,停頓時間比初始標記要長,比並發標記短。

CMS的優點是併發收集、低停頓,也有三個缺點:

  • CMS收集器對CPU資源非常敏感。
  • 無法收集“浮動垃圾”。在併發清理階段使用者執行緒還在執行,還會有新的垃圾不斷產生,這部分垃圾在此次標記之後,無法在這次收集中清理,這部分是“浮動垃圾”。CMS不能像其他收集器那樣等到老年代幾乎完全被填滿了再進行收集,需要預留足夠的執行緒給使用者使用,保守估計,當老年代使用了68%之後CMS收集器就會被啟用。當預留的記憶體無法程式需要,就會出現“Concurrent Mode Failure”失敗,這時虛擬機器將會臨時啟用Serial Old收集器來重新進行老年代的垃圾收集。
  • 最後一個缺點,是“標記-清除”演算法的缺點,會有大量空間碎片。

G1收集器(新生代老年代都可回收)

當今收集器技術發展的最前沿成果之一,其使命是替換CMS收集器。G1具有以下特點:

  1. 並行與併發:充分利用多CPU、多核環境下的硬體優勢,來縮短Stop-The-World停頓的時間。
  2. 分代收集:不需要其他收集器配合就能獨立管理整個GC堆。
  3. 空間整合:整體基於“標記-整理”演算法,區域性基於複製演算法,不會產生記憶體空間碎片。
  4. 可停頓的預測:能讓使用者指定在一個長度為M毫秒的時間片段內,消耗在垃圾收集的時間上不得超過N毫秒。

G1收集時,Java堆記憶體佈局與其他收集器有很大差別,他將Java堆分為多個大小相等的獨立區域(Region),不再有新生代老年代的概念。G1跟蹤各個Region裡面的垃圾堆積的價值(回收所獲的空間大小以及回收所需時間的經驗值),在後天維護一個優先列表,每次根據允許的收集時間,優先回收價值最大的Region。

如何避免全堆掃描

對於Region之間的物件引用以及其他收集器中新生代和老年代之間的物件引用,虛擬機器使用Remembered Set來避免全堆掃描的。Region中被外面引用的物件的相關引用資訊記錄到Remembered Set 中,在進行回收時,在GC根節點的列舉範圍中加入Remembered Set即可保證不對全堆掃描也不會有遺漏。

G1收集運作步驟
  • 初始標記:僅僅標記GC Roots能直接關聯到的物件,並且修改TAMS(Next Top at Mark Start)的值,好讓下一階段使用者並執行時,能在正確的可用的Region中建立新物件。需要執行緒停頓,耗時短。
  • 併發標記:可達性分析,找出存活物件,耗時長,可與使用者程式併發執行。
  • 最終標記:併發標記時會把使用者程式繼續執行而導致的標記變化的記錄記錄在Remembered Set Logs中,最終標記會把這部分日誌整合在Remembered Set中。
  • 篩選回收:先對各個Region的回收價值和成本進行排序,然後根據使用者期待的GC停頓時間來指定回收計劃。

在這裡插入圖片描述