標籤:複製演算法 .com 階段 並且 類的方法 應用 類型 static live
Overview
- 垃圾收集考慮三件事:
- 哪些記憶體需要回收?
- 什麼時候回收?
- 如何回收?
- 重點考慮Java堆中動態分配和回收的記憶體。
Is Object alive?引用計數法
可達性分析演算法
- 在主流商用程式語言(Java, C#, Lisp)的主流實現中,都是通過可達性分析(Reachability Analysis)來判斷對象是否存活的。
- 該演算法的基本思路是通過一系列的稱為"GC Roots"的對象作為起始點,從這些節點開始向下搜尋。搜尋的路徑稱為引用鏈(Reference Chain)。
- 當一個對象到GC Roots沒有任何引用鏈相連(即不可達)時,則該對象是停用。不可達的對象是可回收的。如下:
- 在Java中,可作為GC Roots的對象包括下面幾種:
- 虛擬機器棧(棧幀中的本地變數表)中的引用;
- 方法區中類靜態屬性引用的對象;
- 方法區中常量引用的對象;
- 本地方法棧中JNI引用的對象。
再談引用
- 上面兩種方法都是通過“引用”來判定對象是否alive。
- 但是在JDK1.2之前,Java中的引用定義很傳統:若reference類型的資料中儲存的數值是另一塊記憶體的起始地址,則稱這塊記憶體代表著一個引用。
- 但是考慮這樣的情境:我們希望描述這樣的一類對象,當記憶體空間足夠時,則能保留在記憶體中,否則可拋棄。 <-- 很多系統的緩衝功能都符合這樣的應用情境。
- 於是在JDK1.2之後,Java擴充了引用的概念:將引用分為強引用(Strong Reference), 軟引用(Soft Reference), 弱引用(Weak Reference), 虛引用(Phantom Reference) 4種,強度依次減弱。
- 強引用:最常見的,類似Object obj = new Object()
- 軟引用:用以描述一些還有用但是非必須的對象。對於該類對象,系統在將要發生記憶體溢出異常之前,會回收這些對象。JDK1.2之後提供了SoftReference類。
- 弱引用:也是用以描述非必須對象,但強度更弱於軟引用。被弱引用關聯的對象只能生存到下一次GC之前,而無論當前記憶體是否足夠。
- 虛引用:PhantomReference類。
生存還是死亡
- 即使是在可達性分析演算法中不可達的對象,也並非“非死不可”的。
- 要真正宣告一個對象死亡,至少要經曆兩次標記過程:TBD...
回收方法區
- 一般來說,在方法區進行垃圾收集的"性價比"比較低:在堆中,尤其是新生代中,常規應用一次垃圾收集一般可以回收70%~95%的空間,而永久代的垃圾收集效率遠低於此。
- 永久代的垃圾收集主要回收兩部分:廢棄常量和無用的類。
- 回收廢棄常量與回收Java堆中的對象非常相似。假設一個字串"abc"已經進入常量池,但當前系統中沒有任何一個String對象引用常量池中的"abc"常量,則"abc"常量就會被系統清理出常量池。
- 相比之下判定一個類是否是“無用的類”的條件則相對苛刻很多。需同時滿足:
- 該類所有的執行個體已經被回收;
- 載入該類的ClassLoader已經被回收;
- 該類對應的java.lang.Class對象沒有在任何地方被引用,無法在任何地方通過反射訪問該類的方法。
垃圾收集演算法標記-清除演算法
- 演算法分為“標記”和“清除”兩個階段。
- 該演算法是後續收集演算法的基礎。
- 不足:
- 效率問題:標記和清除兩個過程的效率都不高;
- 空間問題:標記清除後會產生大量不連續的記憶體片段,空間片段太多可能導致以後在程式運行過程中需要分配較大對象時,無法找到足夠的連續記憶體而不得不提前觸發另一次GC。
複製演算法
- 它將可用記憶體按容量劃分為大小相等的兩塊,每次只使用其中的一塊。
- 當使用的塊用完時,就將還存活著的對象複製到另一塊上面,然後再將已使用過的記憶體空間一次清理掉。
- 這樣使得每次都是對整個半區進行記憶體回收,記憶體配置時也不用考慮記憶體片段等情況,只用移動堆頂指標,順序分配即可。
- 代價是記憶體縮小為原來的一半。
- 現在的商業JVM都採用這種收集演算法。IBM的專門研究表明,新生代中的對象98%是“朝生夕死”的,所以並不需要按照1:1的比例劃分記憶體空間。而是將記憶體分為一塊較大的Eden空間和兩塊較小的Survivor空間。
標記-整理演算法
- 複製收集演算法在對象存活率較高時就需要較多的複製操作,效率將會變低。更關鍵的是,若不想浪費一般的空間,就需要有額外的空間進行分配擔保,以應對被使用的記憶體中所有對象都100%存活的極端情況,所以老年代一般不能採用這種演算法。
- 標記-整理演算法:標記過程仍同於標記-清除演算法。但後續步驟不是直接對可回收對象進行清理,而是讓所有存活的對象都向一端移動,然後直接清理掉端外邊的記憶體。
分代收集演算法
- 當前商業JVM的GC都採用“分代收集”(Generational Collection)演算法。
- 這種演算法就是根據對象存貨周期地不同將記憶體劃分為幾塊。
- 一般是把Java堆分為新聲代和老生代,從而根據各個年代的特點採用最適當的收集演算法。
Summary
- 本篇主要覆蓋了GC的兩大問題:
- 哪些對象需要回收:最基礎的是引用計數法,簡單但無法解決循環參考問題。在此基礎上,更常用的是可達性分析演算法。
- 如何回收:最基礎的是標記-清除法,即先標記出哪些對象需要回收,然後逐一清除,不足是會產生大量記憶體片段,從而導致後續可能觸發多次GC。此外更高效的是複製法,該方法確保了連續記憶體,但是將記憶體縮小至原記憶體一般,並且在對象存活率較高時會引入大量複製操作,效率降低。還有標記-整理演算法。 另外,商業JVM都會採用分代收集演算法,即將記憶體劃分為幾塊,根據年代選取合適的收集演算法。
[深入理解Java虛擬機器]<垃圾收集器與記憶體配置策略>