《深入理解Java虛擬機》學習筆記之垃圾收集

一、判斷對象是否存活的算法

1、引用計數算法

特點:判斷對象存活,在對象中添加一個引用計數器,每當有一處引用它,計數加一,引用失效則計數減一。計數爲0,則表示該對象不能被使用。

優點:雖然佔用一些額外的內存空間進行計數,但原理簡單,判定效率較高。

缺點:單純的引用計數很難解決對象之間相互循環引用的問題,往往需要配合大量額外的處理。

2、可達性算法分析

特點:通過一系列名爲GC Roots的根對象作爲起始節點集,從這些節點開始,根據引用關係向下搜索,如果某個對象到GC Roots間沒有任何引用鏈(不可達)時,證明此對象是可回收的。

在這裏插入圖片描述

3、再談引用

引用計數算法和可達性算法,判斷對象是否存活的兩種方法都與引用離不開關係。

JDK1.2之前對引用的定義是:如果reference類型的數據中存儲的數值代表的是另外一塊內存的起始地址,就稱該reference數據代表的是某塊內存、某個對象的引用。

JDK1.2之後對原先較爲狹隘的概念進行了擴充:將引用分爲以下四種,強度由高到低逐漸減弱。

  • 強引用(Strongly Reference):即通常說的引用定義,Object o = new Object();
  • 軟引用(Soft Reference):描述還有用,但不是必須的對象,系統將要發生內存溢出異常前,將會把軟引用關聯的對象列入回收範圍進行二次回收,可以通過SoftReference實現軟引用。
  • 弱引用(Weak Reference):描述非必須的對象,強度較軟引用要弱。弱引用關聯的對象只能生存到下一次垃圾收集發生爲止,也就是說,只要垃圾收集器開始工作,不管你內存是不是足夠,弱引用關聯的對象都會被回收,可以通過WeakReference實現。
  • 虛引用(Phantom Reference):最弱的引用,無法通過該引用獲取一個對象實例,設置該引用指示爲了能在這個對象被回收時受到一個系統通知,可以通過PhantomReference實現。

4、對象啥時候死亡

對象在進行可達性分析後發現沒有與GC Roots相連接的引用鏈,將會被第一次標記,隨後進行依次篩選,篩選的條件是此對象是否有必要執行finalize()方法,以下兩種情況都是判定爲沒有必要執行finalize方法:

  • 對象沒有覆蓋finalize()方法。
  • finalize()方法已經被虛擬機調用過。

如果說有必要執行finalize方法,該對象將被置如F-Queue隊列中,並由虛擬機建立的低調度優先級的Finalizer線程去執行對象們的finalize方法。

finalize方法中,如果對象重新與引用鏈上的任意一個對象建立聯繫,則可以拯救自己,逃脫回收。

稍後收集器將對隊列中的對象進行第二次小規模的標記,此時解救自己的對象將會被移除即將回收的集合。

二、垃圾回收算法

從如何判定對象消亡的角度出發,垃圾收集算法可以劃分爲:

  • 引用計數式垃圾收集,被稱爲直接垃圾收集。
  • 追蹤式垃圾收集,被稱爲間接垃圾收集。

主流的Java虛擬機運用的是追蹤式的垃圾收集算法,以下算法皆屬於該算法大類。

1、分代收集理論

分代收集理論建立在兩個分代假說之上:

  • 弱分代假說:絕大多數對象都是朝生夕滅的。
  • 強分代假說:熬過越多次垃圾收集過程的對象就越難消亡。

兩個分代假說奠定的多款常用垃圾收集器的設計原則: 收集器應該將Java堆劃分出不同的區域,然後將回收對象根據其年齡分配到不同的區域中存儲。

劃分出不同的區域,可以每次只回收部分區域,因此也產生了針對不同區域安排與裏面存儲對象存亡特徵相匹配的垃圾收集算法,如標記-清除、標記-複製、標記整理等算法。

但按照以上的假說和推論,分代收集其實不僅僅是劃分區域那麼簡單,因爲對象不是孤立的,之間甚至可能存在跨代引用的情況,並且在垃圾收集的時候,除了固定的GC Roots之外,還需要遍歷整個老年代的所有對象,才能保證可達性的完全正確,這與老年代中大多數的對象都是不需要被回收的是有矛盾的。

爲了解決該問題,需要引入第三條假說,即跨代引用假說:跨代引用相對於同代引用來說僅佔極少數。

因此,只需要在新生代上建立一個全局的數據結構(記憶集),記憶集將老年代劃分成若干的小塊,標識出老年代的哪一塊內存會存在跨代引用,之後發生Minor GC(新生代收集)時,只有包含了跨代引用的小塊內存裏的對象纔會被加入到GC Roots進行掃描。

2、標記-清除算法

最早出現、最基礎的算法,Mark-Sweep。

【標記】+【清除】:首先標記出所有需要回收的對象,之後統一回收掉所有被標記的對象,或者反一下,標記存活對象,回收沒被標記對象。

【缺點】:

  • 執行效率不穩定,如果Java堆中包含大量對象需要標記或清除,這個過程的執行效率會大大下降。
  • 內存空間的碎片化問題,標記或清除之後將會產生大量的不連續的內存碎片,將會導致之後在分配較大對象的時候,無法找到足夠的連續內存空間而提前觸發垃圾收集。

在這裏插入圖片描述

3、標記-複製算法

半區複製的思想,解決標記-清除算法面對大量可回收對象時,執行效率低的問題。

特點:將可用內存按容量劃分爲大小相等的兩塊,每次只使用其中一塊。當這塊內存用完之後,就將存活的對象複製到另外一塊上面,然後將已使用過的內存空間一次性清理。

【優點】:每次都是針對整個半區進行內存回收,分配內存時不用考慮有空間碎片的複雜情況,只需要移動堆頂指針,按順序分配即可。實現簡單,運行高效。

【缺點】:將可用內存縮小爲原來的一半,空間浪費較大;對象存活率較高時,需要進行較多的複製操作,效率降低。

在這裏插入圖片描述

Appel式回收

將新生代分爲一塊較大的Eden空間和兩塊較小的Survivor空間,每次分配內存只使用Eden和其中一塊Survivor。大多數情況下,HotSpot虛擬機默認的Eden和Survivor的大小比例爲8:1,每次新生代中可用內存空間爲整個新生代容量的90%,此時只會浪費10%的空間。

當然,如果出現超過10%的對象存活,Survivor空間不足以容納依次Minor GC之後存活的對象時,需要其他的內存區域的進行分配擔保。

4、標記-整理算法

標記-整理與標記-清除算法的本質差異:前者是移動式的,後者是非移動式的。

如何體現呢,標記-整理的標記過程和標記-清除算法中的標記過程相同,但後續的操作略有不同:讓所有存活的對象都向內存空間的一端移動,然後直接清理掉邊界以外的內存。

在這裏插入圖片描述

【缺點】:移動大量存活對象時,操作負重。

【優點】:移動並整理對象,相較於標記-清除來說,解決空間碎片化問題。

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