GC和GC算法

目錄

判斷對象是否爲可回收的方法:可達性分析算法

生存還是死亡

垃圾收集

垃圾收集算法

垃圾收集器

按收集對象分類

按是否多線程分類

垃圾收集器詳細說明

詳解分代回收

內存分配的原則

Minor GC、MajorGC、Full GC特點及觸發場景


無特殊說明:以下都是基於HotSpot虛擬機

判斷對象是否爲可回收的方法:可達性分析算法

從所有GC Roots對象開始作爲起始點,向下搜索,搜索走過的路徑稱爲引用鏈,當一個對象到GC Roots對象沒有任何一條引用鏈存在時,證明此對象不可以用,此對象將被判定爲可回收對象。

GC Roots對象包括:

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

生存還是死亡

通過可達性算法判斷可回收的對象是否一定會被回收?

答案是否定的。

判斷一個對象死亡並被回收要經過兩次垃圾收集。

第一次垃圾收集:可達性分析算法中不可達的對象,會被第一次標記並且會判斷需不需要執行對象的finalize()方法。有兩種情況不會執行對象的finalize()方法,第一種是finalize()方法已經被調用過一次,第二種是對象類沒有重寫Object定義的finalize()方法。
如果一個對象被判斷需要執行finalize()方法,那麼該對象會被放置在一個隊列中,並且稍後會由虛擬機創建一個低優先即的線程去執行這個方法,但並不代表這個方法一定會被執行結束。可能是這個方法還沒有執行結束,對象就被回收了。

第二次垃圾收集:對象仍然是不可達的,那麼該對象將被回收。

因此,重寫finalize()方法,並在該方法裏將自己(this關鍵字)賦值給某個類變量或者對象的成員變量,那麼這個對象將不會在第二次垃圾收集時被回收。因此我們也說調用對象的finalize()方法並不代表對象一定會被回收。

垃圾收集

垃圾收集算法

標記-清除算法:就是通過可達性分析和兩次標記判斷對象是否可以死亡,最後在統一回收被標記的對象。
標記-清除算法,效率不高,並且會產生大量的不連續的內存碎片。空間碎片太多可能會導致以後程序運行過程中創建大對象時,無法找到連續的內存而不得不提前觸發一次垃圾收集動作。

複製算法:將內存容量劃分爲大小相等的兩塊,每次只使用其中一塊。當這一塊用完了,就將還存活的對象複製到另外一塊上面,然後再把已用過的內存空間一次清理掉。
這種方式簡單高效,代價就是可用的內存縮小一半。

標記-整理算法:標記過程和標記清除算法的標記過程一樣,不同的是,在標記後續步驟不是直接對可回收對象進行清理,而是讓所有存活的對象都向一端移動,保證所有存活的對象佔用連續的空間,然後直接清理掉最末尾存活對象之後的內存。

分代回收:對堆內存空間,進行劃分,一般分爲年輕代和老年代。在年輕代中每次垃圾收集都會發現有大批對象死去,只有少量存活,因此年輕代採用複製算法,只需要復出少量存活對象的複製成本就可以完成收集。而老年代中因爲對象存活率高,數量多,佔用的空間大,這是採用複製算法,顯然從空間和時間上都是不合適的,因此老年代採用標記-清除或者標記-整理算法進行回收

分代回收爲什麼說一般分爲年輕代和老年代,一般是因爲垃圾收集器G1雖然也是基於分代回收的思想,保留年輕代和老年代的概念,但他並不是把堆物理隔離爲年輕代和老年代,而是把整個堆分爲多個大小相等的獨立區域(Region)。

垃圾收集器

按收集對象分類

年輕代:Serial、ParNew、Parallel Scavenge 基於複製算法

老年代:Serial Old、Parallel Old 這兩種基於標記-整理算法、CMS基於標記清理算法

Region(年輕代+老年代):G1,G1從整體上看基於標記-整理算法,從局部看基於複製算法,因此G1不會產生空間碎片。

按是否多線程分類

串行收集器:指一條垃圾收集線程進行垃圾收集,用戶線程處於等待狀態。Serial、Serial Old是分別用於年輕代和老年代的串行收集器

並行收集器:指多條垃圾收集線程並行工作,而用戶線程仍然處於等待狀態。ParNew、Parallel Scavenge適用於年輕代的並行收集器。Parallel Old是用於老年代的並行收集器。Parallel Scavenge是吞吐量優先的收集器,可以通過配置相關參數來影響吞吐量。

併發收集器:指用戶線程與垃圾收集線程同時執行(不一定是同時也可能交替),用戶線程在繼續運行,垃圾收集線程也在運行。CMS是用於老年代的併發收集器。G1適用於整個堆空間的併發收集器。

串行和並行收集器都會在垃圾收集時造成戶線程停頓。
併發收集器CMS雖然也會使用戶停頓,但因爲CMS在收集過程中存在併發過程允許用戶線程與垃圾收集線程併發執行,所以相對來說停頓時間要小於串行和並行。

垃圾收集器詳細說明

年輕代的三種:

Serial收集器:年輕代單線程收集器,基於複製算法,垃圾收集時會使用戶線程等待。

ParaNew收集器:年輕代多線程並行收集器,基於複製算法,採用多線程進行並行垃圾收集,垃圾收集時會使用戶線程等待。這種收集器會因爲多線程而搶佔CPU資源,因此當只有一個CPU的時候,可能性能還比不上Serial收集器。不過現在都是多核多線程CPU了。

Parallel Scavenge收集器:可以看做ParaNew的吞吐量版本,基於複製算法,它也是多線程並行收集器,不同於ParaNew,Parallel Scavenge收集器更關注吞吐量(吞吐量=運行用戶代碼時間/(運行用戶代碼時間+垃圾收集時間))。

老年代的三種:

Serial Old收集器:Serial收集器的老年代版本,基於標記-整理算法,採用多線程並行進行垃圾收集。垃圾收集時會使用戶線程等待。

Parallel Old收集器:Parallel Scavenge收集器的老年代版本,基於標記-整理算法,採用多線程進行並行垃圾收集。垃圾收集時會使用戶線程等待。

CMS併發收集器:用於垃圾收集時,降低用戶線程等待時間的併發垃圾收集器。

CMS運作過程分爲4步:初始標記==》併發標記==》重新標記==》併發清除

初始標記:單線程標記,會使用戶線程等待。會發生STW(stop the world)。
併發標記:標記線程和用戶線程同時執行。
重新標記:多條標記線程,回使用戶線程等待。會發生STW(stop the world)。
併發清除:清除線程和用戶線程同時執行。

CMS的缺點:

  1. 因爲是併發的可能會和用戶線程爭搶CPU資源,造成吞吐量下降。
  2. 無法處理浮動垃圾,可能會造成“Concurrent Mode Failure”而觸發Full GC的產生,由於併發清理階段用戶線程也在執行,因此這個時候會伴隨程序的運行產生新的垃圾,這部分垃圾稱爲浮動垃圾,而這部分垃圾只有等待下一次GC再清理。
  3. CMS基於標記-清理算法,因此會產生空間碎片,當空間碎片過多,無法找到連續的空間分配大對象時,就會提前觸發Full GC。對於CMS造成的空間碎片的問題,虛擬機提供了一個配置參數 -XX:CMSFullGCsBeforeCompaction,這個參數用於設置執行多少次不壓縮的FullGC後,跟着來一次帶壓縮的(默認值爲0,表示每次進入FullGC是都會進行碎片整理)。碎片整理會使整個Full GC的時間變長。

整個堆分爲多個Region區(保留年輕代和老年代的思想):G1收集器(較爲複雜後續補齊)

綜上所有垃圾收集器的特點,在JVM配置中常常採用下圖的垃圾收集器組合方式:

組合方式:Serial+CMS+SerialOld、Serial+Serial Old 、ParNew+CMS+Serial Old、ParNew+Serial Old、Parallel Scavenge+Serial Old、Parallel Scavenge+Parallel Old(這種是JVM默認的配置)

上面組合可以發現:使用CMS時會搭配Serial Old使用,是因爲CMS會產生內存碎片,使用Serial Old作爲老年代的備用垃圾收集器,Serial Old是基於標記-整理算法的,Serial Old作爲CMS的備胎,幫助整理CMS產生的內存碎片。

Parallel Scavenge和CMS不能搭配使用,因爲他們實現的架構不一樣。

詳解分代回收

Java 的堆內存被分代管理,爲什麼要分代管理呢?分代管理主要是爲了方便垃圾回收,這樣做基於2個事實,第一,大部分對象很快就不再使用;第二,還有一部分不會立即無用,但也不會持續很長時間。

虛擬機堆內存劃分爲年輕代、老年代、和永久代,如下圖所示。

      

  • 年輕代主要用來存放新創建的對象,年輕代分爲 Eden 區和兩個 Survivor 區。大部分對象在 Eden 區中生成。當 Eden 區滿時,還存活的對象會在兩個 Survivor 區交替保存,達到一定次數(這個次數可以通過參數配置,默認15)的對象會晉升到老年代。

  • 老年代用來存放從年輕代晉升而來的,存活時間較長的對象。

  • 永久代,主要保存類信息等內容,這裏的永久代是指對象劃分方式,不是專指 1.7 的 PermGen,或者 1.8 之後的 Metaspace。

內存分配的原則

  1. 對象優先在新生代中Eden區分配,當Eden區沒有空間,虛擬機將發起一次MinorGC
  2. 大對象直接進入老年代。大對象需要大量連續的內存空間。比如很長的字符串,或者很大的數組。
  3. 長期存活的對象將進入老年代。當對象在Eden區分配後,經歷過第一次Minor GC年齡就增加一歲。當年齡超過15(15是默認值)後就會晉升到老年代中。對象晉升老年代的年齡閾值可以通過參數-XX:MaxTenuringThreshold設置
  4. 動態對象年齡判定。虛擬機並不是永遠都要求對象的年齡要達到MaxTenuringThreshold設置的閾值才能晉升老年代,如果在Survivor空間中相同年齡的所有對象大小總和大於Survivor空間的一半,年齡大於或等於該年齡的對象就可以直接進入老年代。
  5. 空間分配擔保,JVM在發生MinorGC之前會檢查老年代最大的可用連續空間是否大於歷次晉升到老年代的對象的平均大小,如果大於,則正常進行一次MinorGC,儘管有“風險”;如果小於,或者HandlePromotionFailure設置不允許空間分配擔保,這時要進行一次FullGC,所以正常情況下爲了避免頻繁FullGC,參數HandlPromotionFailure都是開啓的。
    上面說的“風險”:因爲判斷的是平均大小,有可能這次的晉升對象比平均值大很多;這個時候會導致FullGC。

Minor GC、MajorGC、Full GC特點及觸發場景

Minor GC:清理年輕代,當Eden區空間不足時會觸發Minor GC
Major GC:清理老年代,很多時候MajorGC是由於Full GC觸發的,如果老年代採用CMS收集器,默認情況下老年代佔用比例達到68%時會觸發MajorGC
Full GC: 清理整個堆空間,即觸發MinorGC清理年輕代,觸發MajorGC清理老年代。
Full GC發生的場景,老年代空間不足,而老年代空間不足主要根據垃圾收集器有常見的幾種情況:
1. 如果老年代採用Serial Old或者Parallel Old這兩種基於標記-整理算法的收集器,那麼當整理後的空閒空間不足以分配對象時將觸發FullGC
2.如果老年代採用CMS這種基於標記-清除算法的收集器,那麼可能發生:由於浮動垃圾造成老年代空間不足觸發FullGC,或者由於空間碎片導致沒有足夠大的連續空閒空間分配對象時觸發FullGC
3.空間分配擔保失敗,老年代最大連續可用的空間不足以分配晉升對象時會觸發FullGC

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章