1. 程式人生 > 實用技巧 >Java垃圾回收機制

Java垃圾回收機制

垃圾回收中有兩種經典的方法:

  • 引用計數法,Python中使用的此種演算法,但是解決不了迴圈引用的問題.
  • 可達性演算法,主流java虛擬機器採用此種方法,從GC ROOTS作為起點,不能到達的物件則被判定垃圾

每一個執行緒都有自己獨享的區域,程式計數器,虛擬機器棧,本地方法棧的生命週期都與執行緒一致.因而需要回收的有虛擬機器堆,靜態區以及常量池.

常用的GC演算法

  • mark-sweep標記清除法:

將待清理的垃圾標記後直接清空,會產生許多記憶體碎片.

  • mark-copy標記複製法:

將記憶體對半分,總是空一塊區域.將其中一側的存活物件複製到另一側,然後將這一側全部清空,記憶體浪費比較嚴重.

  • mark-compact標記整理法:

將物件清理後,對存活物件進行整理挪動,避免了上面兩種方法的缺點,但是效率不高.

  • generation-collect分代收集演算法:

HotSpot(JDK7)中的記憶體分佈思想:

將記憶體分成了三塊:年輕代,老年代,永久代.其中年輕代又細分為eden,S1S1(survivor)三個區.

GC的主要過程包括Young GC(minor GC), 它綜合運用了mark-sweep edenmark-copy eden->s0/s1兩種方法,以及Full GC(major GC).

Young GC:

  1. 初始時物件會被分配在eden區:
  2. 隨著時間推移,eden
    區將會越來越滿:
  3. eden區域滿了時,觸發Young GC:

    標識出不可達物件,並將可達物件移至s0.從而清理了eden區域.
  4. eden區域又滿了時,再次觸發Young GC,此時s0也有了垃圾物件:

    s0eden區域的不可達物件標識出,並將可達物件移至s1.

如此循往,有的存活物件在s0s1中不斷移動,因此產生了"代齡".物件在年輕代的三個區中移動,每移動一次,代齡+1.在到達一定閾值(8)後,將晉升至老年代.
還有一種情況是,當物件較大,eden區放不下,將會直接分配到老年代區.

Full GC
當老年代的區域也放滿了,將會觸發Full GC,耗時比較長,此時Java將會暫停所有其他執行緒,會出現應用卡頓的現象,即stop the world

.

經典的垃圾回收器

圖中除G1以外,Serial,ParNew以及Parellel Scavenge都是回收年輕代的垃圾收集器;Serial Old,Parallel Old以及CMS是用來回收老年代的垃圾.除了CMS會部分採用stop th world方式,其餘的都是採用stop the world方式.圖中的連線代表幾種典型的組合應用.

  • Serial: 單執行緒採用mark-copy演算法
  • ParNew: 多執行緒版本Serial
  • Parallel Scavenge: 相比ParNew,提供了-XX:MaxGCPauseMillis(最大垃圾回收停頓時間);-XX:GCTimeRatio(垃圾回收時間與總時間佔比),可以控制吞吐量
  • Serial Old: Serial的老年代版本,單執行緒採用mark-compact,可以作為CMS失敗時的後備選擇
  • Parallel Old: Parallel Scavenge的老年代版本,使用多執行緒以及mark-compact,只能和Parallel Scavenge配合使用
  • CMS: Concurrent Mark Sweep,是多併發的標記清理,主要包括四個階段:
    1. initial mark: 標記GC Root的僅下一級物件,會出現短暫的STW
    2. concurrent mark: 多執行緒向下繼續標識所有關聯物件,不會出現STW
    3. remark: 重新標記一遍步驟2執行時,系統新產的垃圾物件,會出現短暫的STW
    4. concurrent sweep: 多執行緒進行標記清理演算法.在清理的過程中仍然可能會有新垃圾物件產生,此時只能等到下一次的GC了.

CMS將較長的STW分隔成為兩個較短的STW,可以大大改善GC系統卡頓的情況.但是仍然存在以下缺點:

  1. 由於併發進行,CMS在執行回收時會增加對堆記憶體的佔用,即CMS需要在老年代堆記憶體用盡之前完成垃圾回收,否則回收失敗,將會觸發擔保機制,使用serial oldSTW的方式進行一次GC,造成較大停頓.
  2. 無法整理空間碎片,老年代空間會隨著時間推移被逐漸耗盡.
  3. cpu資源敏感
  • G1垃圾回收器:它被設計的初衷是用於替代CMS,與CMS不同,它採用的壓縮演算法,並且採用region的分割槽方式,從而簡化了垃圾收集器,並且減少了記憶體中產生的碎片,也允許使用者指定期望的垃圾回收停頓.

堆分為一系列相同大小的region,在虛擬記憶體中是連續的.每一塊region都有自己的角色,例如eden,survivor或者old,這樣可以方便隨意調整這些角色的區域大小.

G1young gc採用多執行緒的方式,進行清除或者複製,會帶來STW.但是會計算下一次youn gceden區域以及survivor的大小,也會考慮使用者設定的停頓時長.
G1old gc可以分解為以下步驟:

  • 初始標記,會帶來STW
  • GC ROOT開始掃描可達區域
  • 併發標記
  • 再次標記,會帶來STW
  • 併發清除,帶來STW,存活物件較少的region會被優先清除.Young區和Old區的物件有可能會被同時清理,因此也可稱為mixed GC.

References:

一文看懂JVM記憶體佈局及GC原理

詳解 JVM Garbage First(G1) 垃圾收集器

JVM 垃圾收集器Serial +Serial Old+ParNew+Parallel Scavenge+Parallel Old+CMS+G1

Getting Started with the G1 Garbage Collector