Java虛擬機——垃圾收集器和收集算法

1.概念

垃圾收集器(Garbage Collection,GC),回收的條件需要考慮以下

  • 哪些內存需要回收?
  • 什麼時候回收?
  • 如何回收?

2.確定回收對象

回收的對象一般是不被引用的無用對象,那麼如何確定對象沒有被引用了呢?

2.1.引用計數算法

基本思想:給對象添加一個計數器,每當有一個地方引用它時,計數器的值就加1,當引用失效時,計數器就減1,在任何時刻計數器爲0的對象就不可能再被引用。

  • 優點: 這個判定效率很高,實現也簡單
  • 缺點: 有些情況不能處理,例如兩個沒有其他引用的兩個對象互相引用,就造成了循環引用的問題。
2.2.可達性分析算法

基本思想:通過一些列的稱爲“GC Roots”的對象作爲起點,從這些節點開始向下搜索,搜索所走過的路徑稱爲引用鏈(Reference Chain),當一個對象到GC Roots 沒有任何引用鏈相連,則證明這個對象不可用的。
a1到a3爲可用對象,a4-a6爲回收對象
a1到a3爲可用對象,a4-a6爲回收對象

在Java中可以作爲GC Roors 的對象包括下面幾種:

  • 虛擬機棧(棧幀中的本地變量表)中的引用對象。
  • 方法區中類靜態屬性引用的對象。
  • 方法區中常量引用的對象。
  • 本地方法棧中的JNI(即Native方法)引用的對象。

3.垃圾收集算法

3.1.標記-清除算法

算法分爲兩個階段:

  1. 標記:首先標記出所需要回收的對象
  2. 回收:在標記完成後統一回收所有被標記的對象

缺點:

  1. 效率低,標記和清除效率都不高
  2. 空間零碎,標記清除後會產生大量零碎空間,導致分配大對象時不得不提前觸發另一次垃圾回收機制
3.2. 複製算法

爲解決標記-清除算法效率低下的問題,複製算法出現了

基本思想:將內存分爲兩塊等大的空間,每次只是用一塊,當這塊空間用完了,就把還存活的對象複製到另一塊上去,然後整塊回收。

缺點:將內存分爲了兩半,使可用內存急劇減少

優化:每次分爲兩等份,存放活對象的區域會有浪費,年輕代細分把內存區域分成Eden、From survivor 和 To survivor ,比例爲8:1:1,複製算法一般用於年輕代,From survivor 和 To survivor 簡稱爲S0區和S1區,優化如下:

  • Eden + S0區 用於存儲全部對象,S1區用於存儲GC活下來的對象,回收Eden + S0區
  • Eden + S1區 用於存儲全部對象,S0區用於存儲GC活下來的對象,回收Eden + S1區
  • 循環
    這樣的優點就是可以把可用內存控制在 9 / 10 ,提高內存利用率

年輕代爲什麼設置成三個塊?
答:解決了內存碎片問題。

3.3.標記-整理算法

複製算法針對所有對象都存活的極端情況下是不可取的,會造成兩個內存塊之間不斷切換,浪費資源。

標記-整理思想:先對存活對象進行標記,將存活對象都向一端移動,然後清理掉邊界以外的內存。

3.4.分代收集算法

分代收集的思想是將堆中的對象分成兩種情況:新生代和老年代。然後再根據每個分區實行不同的算法。新生代每次都伴有大量對象被回收,故採用複製算法即可達到最大效果,老年代存活的對象較多,採用“標記-整理”或者“標記-清理”算法比較合適

3.5.minor GC、major GC和full GC

在HotSpot虛擬機中存在三種垃圾回收現象,minor GC、major GC和full GC。對新生代進行垃圾回收叫做minor GC,對老年代進行垃圾回收叫做major GC,同時對新生代、老年代和永久代進行垃圾回收叫做full GC。許多major GC是由minor GC觸發的,所以很難將這兩種垃圾回收區分開。major GC和full GC通常是等價的,收集整個GC堆。但因爲HotSpot VM發展了這麼多年,外界對各種名詞的解讀已經完全混亂了,當有人說“major GC”的時候一定要問清楚他想要指的是上面的full GC還是major GC。

4.垃圾收集器

  • 串行垃圾回收器(Serial Garbage Collector)

  • 並行垃圾回收器(Parallel Garbage Collector)

  • 併發標記掃描垃圾回收器(CMS Garbage Collector)

  • G1垃圾回收器(G1 GarbageCollector)

HotSpot虛擬機的垃圾收集器
在這裏插入圖片描述
如果說垃圾回收算法是內存回收的方法論,那垃圾回收器就是內存回收的具體實現

新生代收集器

4.1.Serial 收集器使用“複製”算法

這是一個單線程的收集器,但它並不僅僅使用一個cpu或一個線程去收集,當它進行垃圾回收的時候,必須停止其他工作線程,sun公司把這種情況稱爲“Stop The World” , 但是這種停頓對用戶來說體驗非常差,雖然描述的Serial 收集器很雞肋,但是它依然是虛擬機運行在Client模式下的默認新生代收集器。

  • 優點:簡單高效(與其他收集器的單線程相比),對於限定單cpu來說,它沒有其它線程交互,一心一意撿垃圾。一般在用戶桌面應用場景中,分配給虛擬機管理的內存不會很大,每次收集幾十兆幾百兆的新生代停頓時間一般控制在一百毫秒作用,這對用戶來說影響不大。
4.2 ParNew 收集器使用“複製”算法

ParNew其實就是 Serial 收集器的多線程版本,與Serial相比並沒有太多的改變,唯一重要的一點是它能與CMS(稍後介紹這款收集器)收集器協同工作,CMS是收集線程和用戶線程同時工作,實現了併發收集器,也就是沒有Stop The World 。

4.3 Parallel Scavenge 收集器使用“複製”算法

它也使用複製算法,並行的多線程收集器,這些都和ParNew收集器一樣。但它關注的是吞吐量(CPU用於運行用戶代碼的時間與CPU總消耗時間的比值,虛擬機運行100分鐘,其中垃圾收集用掉1分鐘,吞吐量就是99%),而其它收集器(Serial/Serial Old、ParNew、CMS)關注的是垃圾收集時用戶線程的停頓時間。

老年代收集器

4.4 Serial Old 收集器使用“標記-整理”算法

他是一個單線程收集器,主要意義也是給Client模式下虛擬機使用。

4.5 Parallel Old 收集器使用“標記-整理”算法

它是一個多線程收集器,是Paeallel Scavenge的老年代版本,可與Paeallel Scavenge組合,組成“吞吐量優先”的應用組合。

4.6 CMS 收集器使用“標記-清除”算法

CMS(Concurrent Mark Sweep) ,收集器是一種以最短回收停頓時間爲目標的收集器。
它的運行過程分爲四個步驟:

  • 初始標記
  • 併發標記
  • 重新標記
  • 併發清除

初始標記和重新標記仍需要 Stop The World ,初始標記僅僅是簡單地標記一下能直接關聯到的對象,併發標記就是進行GC Roots Tracing 的過程了,重新標記就是修正併發標記期間用戶程序繼續運行程序而產生的一些對象變動,這個階段停頓時間比初始標記時間稍長,但是明顯比並發標記時間短。

因爲耗時最長的併發標記是與用戶程序併發執行的,所以CMS是一款低停頓的收集器。

缺點:

  • CMS 收集器對CPU資源非常敏感。併發就會對CPU造成負擔
  • CMS 收集器無法處理浮動垃圾,可能出現“Concurrent Mode Failure”失敗而導致出現另一次 Full GC 產生。
  • “標記-清除”算法產生的大量零碎空間碎片。
4.7 G1 收集器使用“標記-整理”算法

特點:

  • 並行與併發
  • 分代收集:採用與其他GC不同的分代形式,採用不用的方式去處理新建對象和已經存活一段時間的對象,對熬過多次GC的對象比較友好
  • 空間整合:G1從整體來看是基於“標記-整理”算法實現的收集器,但從局部(兩個Region之間)來看是“複製”算法的收集器,無論哪種算法都意味着沒有碎片空間產生,這種算法有利於程序長時間運行,當分配大對象的時候不會因無大空間而觸發GC
  • 可預測的停頓:能讓使用者明確指定在一個長度爲M毫秒的時間片內GC的時間不會超過N毫秒。

雖然G1分代,但是他不再是侷限於新生代和老年代這兩個區域,而是將整個Java堆分成多個大小相等的獨立區域,新生代和老年代的概念被放入了這多個區域裏,這些區域不再是物理隔離,G1之所以能預測停頓,是因爲他在有計劃地避免整個Java堆進行全區域GC。

這樣就真的是以Region爲單位進行GC了嗎?Region不能孤立工作,對象之間或多或少有些聯繫,不可能所有有聯繫的對象都預先分配到一起,這是不可能的,所以只掃描一個Region不能確認對象是否存活。

G1收集器的運作大致可以分爲:

  • 初始標記
  • 併發標記
  • 最終標記
  • 篩選回收
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章