垃圾收集是一項自動化的技術。但是當咱們排查各類內存問題,或者當垃圾收集成爲系統達到更高併發量的瓶頸時,咱們須要對這些本來自動化的技術進行必要的監控和調節,全部咱們頗有必要學習 JVM 的垃圾收集機制。算法
垃圾回收也稱爲 GC (Garbage Collection),或者能夠稱爲垃圾收集。緩存
對於線程私有的三個部分(程序計數器,虛擬機棧和本地方法棧),不怎麼須要考慮回收問題,緣由:bash
對於線程共享的兩個部分(堆和方法區,主要是堆),須要考慮回收,緣由:併發
對於堆來講,若是不進行垃圾回收,內存早晚都會被消耗空,由於咱們在不斷的分配內存空間而不進行回收。除非內存無限大,咱們能夠任性的分配而不回收,可是事實並不是如此。因此,垃圾回收是必須的。高併發
在對堆進行垃圾回收前,必須肯定每一個對象是否還存活着;而這個判斷過程主要是如下兩種算法學習
給對象添加一個引用計數器,每當有一個地方引用它,計數器值加 1;每當引用失效,計數器減 1;當某個對象任什麼時候候計數器值都是 0 時,這個對象就「死」了ui
缺點:很難解決對象之間循環引用的問題,也所以主流的 JVM 都沒有使用該算法來管理內存spa
public class GCTest {
Object object;
private static void test() {
GCTest test1 = new GCTest();
GCTest test2 = new GCTest();
test1.object = test2;
test2.object = test1;
test1 = null;
test2 = null;
}
}
複製代碼
相似這樣的例子,因爲 test1 和 test2 相互引用對方,即便這兩個對象已經不可能再被訪問到(兩個變量都已經指向 null),引用計數算法也沒法讓垃圾收集器對它們進行回收線程
這是主流 JVM 使用的回收算法code
經過一系列稱爲 GC Roots 的對象做爲起始點,從這些節點向下搜索,若是一個對象與 GC Roots 對象有引用鏈相連,說明對象可用;反之,對象不可用
如上圖的對象 4,5,6 則須要被回收
而可做爲 GC Roots 的對象包括下面幾種:
方法區不多進行垃圾回收,甚至能夠不要求虛擬機對方法區進行回收,由於能回收的東西不多,所以也叫作永久代
在永久代,主要回收兩個內容:廢棄常量,無用的類。若是在永久代發生垃圾回收,那麼這兩個內容就會被清理出去(固然大多數狀況下不會去對永久代進行垃圾回收操做)
不論使用什麼算法判斷對象的存活狀況,這都和「引用」息息相關
Object o = new Objrct
這樣的引用能夠經過 SoftReference 實現,這是 sf 對 obj 有軟應用
Object obj = new Object();
SoftReference<Object> sf = new SoftReference<Object>(obj);
sf.get(); //若是 obj 被標記爲須要被回收,則會返回null
複製代碼
SoftReference 能夠用來實現相似緩存的功能
能夠經過 WeakReference 實現,一般用於監控對象是否已經被垃圾回收器標記爲即將回收的垃圾
Object obj = new Object();
WeakReference<Object> wf = new WeakReference<Object>(obj);
wf.get();
wf.isEnQueued();//返回是否被垃圾回收器標記爲即將回收的垃圾
複製代碼
能夠經過 PhantomReference 實現,主要用於檢測對象是否已經從內存中刪除
Object obj = new Object();
PhantomReference<Object> pf = new PhantomReference<Object>(obj);
pf.get();//永遠返回null
pf.isEnQueued();//返回是否從內存中已經刪除
複製代碼
先標記出要回收的對象,標記完成後統一清除這些對象。
缺點:
將內存劃分爲等大的兩塊,一次只使用一塊。當其中一塊用完了,就把裏面的存活的對象所有複製到另外一塊去,而後將已經使用的那一大塊一次性所有清理掉
標記——清除算法的改進,在完成標記以後,讓全部存活的對象都向一端移動,而後直接清理掉邊界之外的內存,故名叫「整理」。
當前商業虛擬機的垃圾收集都採用「分代收集」算法
分代收集算法將內存劃分爲新生代和老年代:
在新生代中,每次垃圾收集都發現有大批對象死去,只有少許存活,那就選用複製算法,只須要付出少許存活對象的複製成本就能夠完成收集
在老年代中由於對象存活率高、沒有額外空間對它進行擔保,就必須採用「標記 — 清理」或者「標記 — 整理」算法來回收。