淺談Java垃圾回收

上一次咱們已經介紹了Java內存模型,今天來簡單介紹一下Java的垃圾回收機制。java

前沿

Java 的自動內存管理主要是針對對象內存的回收和對象內存的分配。同時,Java 自動內存管理最核心的功能是內存中對象的分配與回收。git

Java 堆是垃圾收集器管理的主要區域,所以也被稱做GC 堆(Garbage Collected Heap)github

堆內存常見分配策略

  • 對象優先在eden區分配
  • 大對象直接進入老年代
  • 長期存活的對象將進入老年代

對象優先在eden區分配

目前主流的垃圾收集器都會採用分代回收算法,所以須要將堆內存分爲新生代老年代,這樣咱們就能夠根據各個年代的特色選擇合適的垃圾收集算法。算法

大多數狀況下,對象在新生代中eden區分配。當eden區沒有足夠空間進行分配時,虛擬機將發起一次 Minor GC.segmentfault

咱們先來看看 Minor GC 和 Full GC 有什麼不一樣呢?數組

  • 新生代 GC(Minor GC):指發生新生代的的垃圾收集動做,Minor GC 很是頻繁,回收速度通常也比較快。
  • 老年代 GC(Major GC/Full GC):指發生在老年代的 GC,出現了 Full GC 常常會伴隨至少一次的 Minor GC(並不是絕對),Full GC 的速度通常會比 Minor GC 的慢 10 倍以上。

大對象直接進入老年代

大對象就是須要大量連續內存空間的對象(好比:字符串、數組)。服務器

爲何要這樣呢?
這樣作的目的是避免在Eden區及兩個Survivor區之間發生大量的內存複製。多線程

長期存活的對象將進入老年代

既然虛擬機採用了分代收集的思想來管理內存,那麼內存回收時就必須能識別哪些對象應放在新生代,哪些對象應放在老年代中。爲了作到這一點,虛擬機給每一個對象一個對象年齡(Age)計數器。閉包

若是對象在 Eden 出生並通過第一次 Minor GC 後仍然可以存活,而且能被 Survivor 容納的話,將被移動到 Survivor 空間中,並將對象年齡設爲 1.對象在 Survivor 中每熬過一次 MinorGC,年齡就增長 1歲,當它的年齡增長到必定程度(默認爲 15 歲),就會被晉升到老年代中。對象晉升到老年代的年齡閾值,能夠經過參數-XX:MaxTenuringThreshold來設置。併發

注意:爲了更好的適應不一樣程序的內存狀況,虛擬機不是永遠要求對象年齡必須達到了某個值才能進入老年代, 若是 Survivor 空間中相同年齡對象大小的總和大於 Survivor 空間的一半,年齡大於或等於該年齡的對象就能夠直接進入老年代,無需達到要求的年齡

對象死亡

堆中幾乎放着全部的對象實例,對堆垃圾回收前的第一步就是要判斷那些對象已經死亡(即不能再被任何途徑使用的對象)。

引用計數法

給對象中添加一個引用計數器,每當有一個地方引用它,計數器就加 1;當引用失效,計數器就減 1;任什麼時候候計數器爲 0 的對象就是不可能再被使用的。

這個方法實現簡單,效率高,可是目前主流的虛擬機中並無選擇這個算法來管理內存,其最主要的緣由是它很難解決對象之間相互循環引用的問題。

可達性分析算法

這個算法的基本思想就是經過一系列的稱爲 「GC Roots」 的對象做爲起點,從這些節點開始向下搜索,節點所走過的路徑稱爲引用鏈,當一個對象到 GC Roots 沒有任何引用鏈相連的話,則證實此對象是不可用的。

1.jpg

垃圾回收算法

目前垃圾收集算法有如下幾種:

  • 標記-清除算法
  • 複製算法
  • 標記-整理算法
  • 分代收集算法

標記-清除算法

該算法分爲「標記」「清除」階段:首先標記出全部須要回收的對象,在標記完成後統一回收全部被標記的對象。它是最基礎的收集算法,後續的算法都是對其不足進行改進獲得。這種垃圾收集算法會帶來兩個明顯的問題:

  1. 效率問題
  2. 空間問題(標記清除後會產生大量不連續的碎片)

2.jpg

複製算法

爲了解決效率問題,「複製」收集算法出現了。它能夠將內存分爲大小相同的兩塊,每次使用其中的一塊。當這一塊的內存使用完後,就將還存活的對象複製到另外一塊去,而後再把使用的空間一次清理掉。這樣就使每次的內存回收都是對內存區間的一半進行回收。

4.jpg

標記-整理算法

根據老年代的特色提出的一種標記算法,標記過程仍然與「標記-清除」算法同樣,但後續步驟不是直接對可回收對象回收,而是讓全部存活的對象向一端移動,而後直接清理掉端邊界之外的內存。

5.jpg

分代收集算法

當前虛擬機的垃圾收集都採用分代收集算法,這種算法沒有什麼新的思想,只是根據對象存活週期的不一樣將內存分爲幾塊。通常將 java 堆分爲新生代和老年代,這樣咱們就能夠根據各個年代的特色選擇合適的垃圾收集算法。

好比在新生代中,每次收集都會有大量對象死去,因此能夠選擇複製算法,只須要付出少許對象的複製成本就能夠完成每次垃圾收集。而老年代的對象存活概率是比較高的,並且沒有額外的空間對它進行分配擔保,因此咱們必須選擇「標記-清除」或「標記-整理」算法進行垃圾收集。

垃圾收集器

若是說收集算法是內存回收的方法論,那麼垃圾收集器就是內存回收的具體實現。

接下來咱們來了解一下各類垃圾收集器,這樣才能根據具體應用場景選擇適合本身的垃圾收集器。

Serial收集器

Serial(串行)回收器收集器是最基本、歷史最悠久的垃圾收集器了。你們看名字就知道這個收集器是一個單線程收集器了。它的 「單線程」 的意義不只僅意味着它只會使用一條垃圾收集線程去完成垃圾收集工做,更重要的是它在進行垃圾收集工做的時候必須暫停其餘全部的工做線程,直到它收集結束

新生代採用複製算法,老年代採用標記-整理算法。

11.jpg

ParNew收集器

ParNew 收集器其實就是 Serial 收集器的多線程版本,除了使用多線程進行垃圾收集外,其他行爲(控制參數、收集算法、回收策略等等)和 Serial 收集器徹底同樣。

新生代採用複製算法,老年代採用標記-整理算法。
ParNew 收集器

Parallel Scavenge收集器

Parallel Scavenge 收集器也是使用複製算法的多線程收集器,它看上去幾乎和ParNew都同樣。

Parallel Scavenge 收集器關注點是吞吐量(高效率的利用 CPU)。CMS 等垃圾收集器的關注點更多的是用戶線程的停頓時間(提升用戶體驗)。所謂吞吐量就是 CPU 中用於運行用戶代碼的時間與 CPU 總消耗時間的比值。

新生代採用複製算法,老年代採用標記-整理算法。

aa.jpg

Serial Old收集器

Serial 收集器的老年代版本,它一樣是一個單線程收集器。它主要有兩大用途:一種用途是在 JDK1.5 以及之前的版本中與 Parallel Scavenge 收集器搭配使用,另外一種用途是做爲 CMS 收集器的後備方案。

Parallel Old收集器

Parallel Scavenge 收集器的老年代版本。使用多線程和「標記-整理」算法。在注重吞吐量以及 CPU 資源的場合,均可以優先考慮 Parallel Scavenge 收集器和 Parallel Old 收集器。

CMS收集器

CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時間爲目標的收集器。它很是符合在注重用戶體驗的應用上使用。

CMS(Concurrent Mark Sweep)收集器是 HotSpot 虛擬機第一款真正意義上的併發收集器,它第一次實現了讓垃圾收集線程與用戶線程(基本上)同時工做。

CMS 收集器是一種「標記-清除」算法實現的,它的運做過程相比於前面幾種垃圾收集器來講更加複雜一些。整個過程分爲四個步驟:

  • 初始標記:暫停全部的其餘線程,並記錄下直接與 root 相連的對象,速度很快 ;
  • 併發標記:同時開啓 GC 和用戶線程,用一個閉包結構去記錄可達對象。但在這個階段結束,這個閉包結構並不能保證包含當前全部的可達對象。由於用戶線程可能會不斷的更新引用域,因此 GC 線程沒法保證可達性分析的實時性。因此這個算法裏會跟蹤記錄這些發生引用更新的地方。
  • 從新標記:從新標記階段就是爲了修正併發標記期間由於用戶程序繼續運行而致使標記產生變更的那一部分對象的標記記錄,這個階段的停頓時間通常會比初始標記階段的時間稍長,遠遠比並發標記階段時間短
  • 併發清除:開啓用戶線程,同時 GC 線程開始對爲標記的區域作清掃。

1,jpg.jpg

從它的名字就能夠看出它是一款優秀的垃圾收集器,主要優勢:併發收集、低停頓。可是它有下面三個明顯的缺點:

  • 對 CPU 資源敏感;
  • 沒法處理浮動垃圾;
  • 它使用的回收算法-「標記-清除」算法會致使收集結束時會有大量空間碎片產生。

G1收集器

G1 (Garbage-First) 是一款面向服務器的垃圾收集器,主要針對配備多顆處理器及大容量內存的機器. 以極高機率知足 GC 停頓時間要求的同時,還具有高吞吐量性能特徵.

被視爲 JDK1.7 中 HotSpot 虛擬機的一個重要進化特徵。它具有一下特色:

  • 並行與併發:G1 能充分利用 CPU、多核環境下的硬件優點,使用多個 CPU(CPU 或者 CPU 核心)來縮短 Stop-The-World 停頓時間。部分其餘收集器本來須要停頓 Java 線程執行的 GC 動做,G1 收集器仍然能夠經過併發的方式讓 java 程序繼續執行。
  • 分代收集:雖然 G1 能夠不須要其餘收集器配合就能獨立管理整個 GC 堆,可是仍是保留了分代的概念。
  • 空間整合:與 CMS 的「標記--清理」算法不一樣,G1 從總體來看是基於「標記整理」算法實現的收集器;從局部上來看是基於「複製」算法實現的。
  • 可預測的停頓:這是 G1 相對於 CMS 的另外一個大優點,下降停頓時間是 G1 和 CMS 共同的關注點,但 G1 除了追求低停頓外,還能創建可預測的停頓時間模型,能讓使用者明確指定在一個長度爲 M 毫秒的時間片斷內。

G1 收集器的運做大體分爲如下幾個步驟:

  • 初始標記
  • 併發標記
  • 最終標記
  • 篩選回收

G1 收集器在後臺維護了一個優先列表,每次根據容許的收集時間,優先選擇回收價值最大的 Region(這也就是它的名字 Garbage-First 的由來)。這種使用 Region 劃份內存空間以及有優先級的區域回收方式,保證了 GF 收集器在有限時間內能夠儘量高的收集效率(把內存化整爲零)。

參考

JVM垃圾回收機制

相關文章
相關標籤/搜索