Java虛擬機學習之垃圾收集器

一、爲什麼要學習GC技術

垃圾收集也稱GC,是Java內存動態分配和回收背後的關鍵技術,有了垃圾收集器,Java程序員就與C++程序員有了巨大的區別,C++程序員需要謹慎的申請和釋放內存,而Java程序員就不需要那麼辛苦了,但是我們還是需要了解GC技術,當我們需要排除各種內存溢出,內存泄漏問題時,我們就需要對這些自動化技術實施必要的監控和調節。


二、判斷對象存活狀態算法

1、引用計數算法
給對象添加一個引用計數器,每當有一個地方引用它時,計數器值就加一;當引用失效時,計數器值就減一,任何時刻計數器爲0的對象就是不可能再被使用的。

缺點:無法解決相互循環引用的問題。
public class ReferenceCountingGC {

    public Object instance = null;

    private static final int _1MB = 1024 * 1024;

    private byte[] bigSize = new byte[2 * _1MB];

    public static void main(String[] args) {
        ReferenceCountingGC a = new ReferenceCountingGC();
        ReferenceCountingGC b = new ReferenceCountingGC();
        a.instance = b;
        b.instance = a;
        a = null;
        b = null;
        //此時若發生GC,若虛擬機使用的是引用計數法,則無法將a與b標記爲死亡對象
        System.gc();
    }
}

注意:在調用System.gc()時,只是通知系統可以進行垃圾收集,系統並不會立即執行GC,而是會等待一些條件發生纔會執行,一般編程中不需要調用這個方法,因爲GC是一件虛擬機自動進行的事情。

2、可達性分析算法
通過一系列的稱爲“GC Roots”的對象作爲起始點,從這些節點開始向下搜索,搜索所走過的路徑稱爲引用鏈,當一個對象到GC Roots沒有任何引用鏈相連時,則證明此對象是不可用的。

(圖片來自網絡)

三、引用的類型

1、強引用
是指在程序代碼中pub存在的,類似“Object obj = new Object()”這類引用,只要強引用還在,垃圾收集器永遠不會回收掉被引用的對象。

2、軟引用
描述一些還有用但並非必須的對象。對於軟引用關聯着的對象,在系統將要發生內存溢出異常之前,將會把這些對象列進回收範圍之中進行第二次回收。如果這次回收還沒有足夠的內存,纔會拋出內存溢出異常。SoftReference。

3、弱引用
也是用來描述非必須對象的,但是它的強度比軟引用更弱一些,被弱引用關聯的對象只能生存到下一次垃圾收集發生之前。當垃圾收集器工作時,無論前內存是否足夠,都會回收掉只被弱引用關聯的對象。WeakReference。

4、虛引用
虛引用也稱爲幽靈引用,是最弱的一種引用關係。一個對象是否有虛引用的存在,完全不會對其生存時間構成影響,也無法通過虛引用來去的一個對象實例。

四、對象的自我拯救

即使在可達性分析算法中不可達的對象,也並非是“非死不可”的,要真正宣告一個對象死亡,至少要經歷兩次標記過程:
1、若一個對象沒有與GCRoots相連接的引用鏈,將會被第一次標記並且進行一次篩選,篩選的條件是此對象是否有必要執行finalize()方法。當對象沒有覆蓋finalize()方法,或者finalize()方法以及被虛擬機調用過,這兩種方法視爲沒必要執行。若沒必要執行,則直接GC。
2、如果這個對象被判定爲有必要執行finalize(),那麼該對象將會被防止在一個叫F-Queue的隊列之中,並在稍後由一個由虛擬機自動建立的、低優先級的Finalizer線程去執行它。在執行finalize()時,該對象可以自救。(即賦值給某個引用)

五、回收方法區

方法區也稱爲“永久代”。永久代的垃圾收集主要回收兩部分內容:廢棄常量和無用的類。

六、垃圾收集算法

1、標記—清除算法(Mark-Sweep)
分爲兩個階段:標記和清除。
首先標記處所有需要回收的對象,在標記完成後統一回收的對象,在標記完成後統一回收所有標記的對象。
不足之處:
效率問題,標記和清除兩個過程的效率都不高。
空間問題,標記清除之後會產生大量不連續的問題。 

(圖片來自網絡)
2、複製算法
爲了解決效率問題,複製算法出現了。將內存分爲大小相等的兩塊,每次只使用其中的一塊。當這一塊內存用完了,就將還存活着的對象複製到另一塊上面,然後再把已使用過的內存空間一次清理掉。
優點:這樣使得每次都是對整個半區進行內存回收,內存分配時不用考慮內存碎片等複雜情況,只要移動堆頂指針,按順序分配內存即可,實現簡單,運行高效。
缺點:內存縮小爲原來的一半,空間消耗。


(圖片來自網絡)
但是一般來說,不會分配一半。
現在的商業虛擬機都採用這種收集算法來回收新生代,新生代中的對象98%是“朝生夕死”的,所以並不需要按照1:1的比例劃分內存空間,而是將內存分爲一塊較大的Eden空間和兩塊較小的Survivor空間,每此使用Eden和其中一塊Survivor。
當回收時,將Eden和Survivor中還存活着的對象一次性地賦值到另一塊Survivor空間上,最後清理掉Eden和剛纔用過的Survivor空間。HotSpot虛擬機默認Eden和Survivor的大小比例是8:1。
當Survivor空間不夠用時,需要依賴其他內存(老年代)進行擔保分配,這些對象將直接通過分配擔保機制進入老年代。

3、標記—整理算法(Mark-Compact)
複製收集算法在對象存活率較高時就要進行較多的複製操作,效率會降低。
在標記後,讓所有存活對象都移動到一端,然後直接清理掉端邊界以外的內存。


(圖片來自網絡)

4、分代收集算法
一般吧Java堆分爲新生代和老年代,根據各個年代的特點採用最適當的收集算法。
在新生代中,每次垃圾收集時都發現有大批量對象死去,只有少量存活,就選用複製算法。
在老年代中,因爲對象存活率高,沒有額外空間對他進行分配擔保,就必須使用“標記-清理”或者“標記-整理算法”進行回收。   

七、垃圾收集器

1、Serial收集器(新生代)
是一個單線程收集器,“單線程”的意義並不僅僅說明它只會使用一個CPU或一條收集線程去完成垃圾收集工作,更重要的是它在進行垃圾收集時,必須暫停其他所有工作線程,直到它收集結束。可以與老年代的CMS收集器合作。

優點:簡單而高效(與其他收集器的單線程比),對於限定單個CPU環境來說,Serial收集器由於沒有線程交互的開銷,專心做垃圾收集自然可以獲得最高的單線程收集效率。

缺點:有停頓時間,不能與用戶線程並行工作。


2、ParNew收集器(新生代)
是Serial收集器的多線程版本,可與CMS收集器合作。
在單CPU環境下,性能不如Serial。
在CPU越來越多的情況下,性能越來越強。


3、Parallel Scavenge(新生代)
使用複製算法,關注點與其他收集器不同,關注吞吐量,吞吐量優先。
吞吐量=運行用戶代碼時間 / (運行用戶代碼時間 + 垃圾收集時間)
兩個重要參數:
-XX:MaxGCPauseMillis (大於0的整數) 收集器停頓時間,收集器儘可能地保證內存回收話費的時間不超過設定值。GC停頓時間縮短是以犧牲吞吐量和新生代空間來換取得。
-XX:GCTimeRatio  (0到100的整數)即吞吐量的倒數。

4、Serial Old(老年代)
使用“標記-整理”算法。
兩種用途:
與Parallel Scavenge 收集器搭配使用。
作爲CMS收集器的後備預案。

5、Parallel Old(老年代)
是Parallel Scavenge的老年代版本。
與Parallel Scavenge這個吞吐量優先收集器合作使用。

6、CMS收集器(老年代)Concurrent Mark Sweep
Mark-Sweep,標記—清除算法
以獲取最短回收停頓時間爲目標的收集器。重視響應速度,常用於B/S系統的服務端。
4個步驟:
初始標記:需要Stop The World。僅僅標記一下GC Roots 能直接關聯到的對象。速度很快。
併發標記:GC Roots Tracing的過程。
重新標記:需要Stop The World。爲了修正併發標記期間因用戶程序繼續運作而導致標記產生變動的那一部分對象的標記記錄。
併發清除:與用戶線程併發進行清除。

最耗時的標記和清除都是可以與用戶線程併發的,因此總體來說CMS收集器的內存回收過程是與用戶線程一起併發執行的。
也稱之爲併發低停頓收集器。
三大缺點:
(1)CMS收集器對CPU資源非常敏感,在併發階段,雖然不會導致用戶線程停頓,但是因爲佔用了一部分線程,導致應用程序變慢,總吞吐量降低。CMS默認啓動回收線程數是  (CPU數量+3)/4。

(2)CMS收集器無法處理浮動垃圾,可能出現“Concurrent Mode Failure”失敗而導致另一次Full GC產生。由於垃圾收集階段用戶線程還需要運行,那也就還需要預留足夠的內存空間使用,因此CMS收集器不能像其他收集器那也等到老年代幾乎完全被填滿了在進行收集。要是CMS運行期間預留的內存無法滿足程序需要,就會出現“Concurrent Mode Failure”失敗。

(3)基於“標記-清除”算法實現的收集器,會產生大量空間碎片。

7、G1收集器(整個堆內存)

主要特點:
(1)併發與並行
G1能重複利用多CPU環境下的硬件優勢,部分其他收集器原本需要停頓Java線程執行的GC動作,G1收集器仍然可以通過併發的方式讓Java程序繼續執行。
(2)空間整合
從整體上來看是基於“標記-整理”,從局部上來看(兩個Region)來看,是基於“複製”,總而言之不會產生空間碎片。
(3)可預測的停頓
G1除了降低停頓外,還能建立可預測的停頓時間模型,能明確在一個長度爲M毫秒的時間片段內,消耗在垃圾收集上的時間不得超過N毫秒。

實現方式:
G1收集器收集的是整個堆內存,將堆劃分成多個大小相等的獨立區域(Region),可以有計劃地避免在整個堆中進行全區域垃圾回收,G1跟蹤谷歌Region裏面的垃圾堆積喫的價值大小,在後臺維護一個優先列表,每次根據收集時間,優先回收價值最大的Region。
化整爲零的思路。
虛擬機用RemeberedSet 來避免Region之間的對象引用。
四個步驟:
1、初始標記
2、併發標記
3、最終標記
4、篩選回收
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章