若是要問Java與其餘編程語言最大的不一樣是什麼,我第一個想到的必定就是Java所運行的JVM所自帶的自動垃圾回收機制,如下是我學習JVM垃圾回收機制整理的筆記,但願能對讀者有一些幫助。
<!-- more -->java
如何判斷對象已死?有兩種算法算法
給對象添加一個計數器,每當有一個地方引用它時,計數器的值就加一,當引用失效的時候,計數器就減一 ,任什麼時候刻計數器爲0的對象就是不可能再被使用的時候。編程
這個算法看似不錯並且簡單,不過存在這一個致命傷(當兩個對象互相引用的時候,就永遠不會被回收)安全
public class Obj{ public Object instance=null; } Obj a=new Obj(); Obj b=new Obj(); a.instance=b; b.instance=a; a=null; b=null;
因而引用計數算法就永遠回收不了這兩個對象,下面介紹另外一種算法。併發
經過一系列被稱爲「GC Roots」的對象做爲起始點,從這些接點向下搜索,搜索所走過的路徑稱爲引用鏈,當一個對象與任何一個引用鏈沒有關聯的時候則能夠被回收。編程語言
Java中,可做爲GC Roots的對象包括下面集中學習
不管是計數器算法仍是可達性分析算法,都與「引用」有關,下面是Java中4種引用,強度依次減弱線程
強引用就是咱們平時最熟悉的code
Object obj=new Object();
只要強引用還存在,垃圾收集器永遠不會回收掉被引用的對象。對象
發生gc的時候,若是JVM內存充足則不回收,用SoftReference類來實現軟引用。展現一個例子
SoftReference<Object> softReference=new SoftReference<>(new Object()); System.out.println("before gc "+softReference.get()); System.gc(); System.out.println("after gc "+softReference.get());
如下是輸出
before gc java.lang.Object@2752f6e2 after gc java.lang.Object@2752f6e2
能夠看到軟引用的對象依然還在。
一旦發生gc,不管JVM內存充足與否,都會回收掉,用WeakReference類來實現弱引用。展現一個例子
WeakReference<Object> softReference=new WeakReference<>(new Object()); System.out.println("before gc "+softReference.get()); System.gc(); System.out.println("after gc "+softReference.get());
如下是輸出
before gc java.lang.Object@2752f6e2 after gc null
弱引用的對象已經被回收!
虛引用也稱爲幽靈引用或者幻影引用,是最弱的一種引用,沒法經過虛引用來取得一個對象實例,擁有虛引用的對象能夠在任什麼時候候被垃圾回收器回收。惟一的目的就是能在當這個對象被回收的時候收到一個系統通知,可用PhantomReference類實現虛引用。
垃圾回收大多發生在Heap(堆區),由於在方法區進行垃圾回收效率較低,要斷定一個類是不是「無用的類」條件比較苛刻,類須要同時知足下面3個條件才能算是無用的類
首先,GC又分爲minor GC 和 Full Gc(也稱爲Major GC)。Java 堆內存分爲新生代和老年代,新生代中又分爲1個Eden區域 和兩個 Survivor區域。
那麼對於 Minor GC 的觸發條件:大多數狀況下,直接在 Eden 區中進行分配。若是 Eden區域沒有足夠的空間,那麼就會發起一次 Minor GC;對於 Full GC(Major GC)的觸發條件:也是若是老年代沒有足夠空間的話,那麼就會進行一次 Full GC。
注意:上面所說的只是通常狀況,實際上,須要考慮一個空間分配擔保的問題:
在發生Minor GC以前,虛擬機會先檢查老年代最大可用的連續空間是否大於新生代全部對象的總空間。若是大於則進行Minor GC,若是小於則看HandlePromotionFailure設置是否容許擔保失敗(不容許則直接Full GC)。若是容許,那麼會繼續檢查老年代最大可用的連續空間是否大於歷次晉升到老年代對象的平均大小,若是大於則嘗試Minor GC(若是嘗試失敗也會觸發Full GC),若是小於則進行Full GC。
System.gc()就是指的Full GC
可是,具體程序執行到什麼位置纔會自動gc,這兒提兩個概念Safepoint和SafeRegion。
安全點的選定是以程序「是否具備讓程序長時間執行的特徵」爲標準選定的,「長時間執行」最明顯的特徵就是
對於Safepoint如何在GC發生時讓全部線程都跑到最近的安全點上停下來,有兩種方案搶先式中斷和主動式中斷。
搶先式中斷:不須要線程執行的代碼主動配合,GC發生時,首先把全部線程所有中斷,若是發現有線程中斷的地方不在安全點上,就恢復線程,讓它「跑到」安全點上。(這種方案几乎不多使用)
主動式中斷 :當GC須要中斷線程的時候,不直接對線程操做,僅僅簡單設置一個標誌,各個線程執行時主動去輪詢這個標誌,發現爲true就自動掛起,輪詢的地方和安全點是重合的。
當線程處於Sleep狀態或者Blocked狀態,這時候線程沒法響應JVM的中斷請求,「走」到安全的地方去中斷掛起,JVM也顯然不太可能等待線程從新被分配CPU時間,這時候就須要安全區域來解決。
當線程執行到Safe Region中的代碼時,首先標識本身已經進入了Safe Region,當JVM在發起GC時,就不用管標識本身爲Safe Region狀態的線程了。在線程要離開Safe Region時,他要檢查系統是否完成了整個GC過程,若是完成了,線程就繼續執行,不然必須等待直到收到能夠安全離開Safe Region的信號爲止。
算法分爲標記和清除兩個階段,首先標記出全部須要回收的對象,在標記完成後統一回收全部被標記的對象。他的不足主要有兩個:
複製算法能夠將容量劃分爲大小相等的兩塊,每次只使用其中的一塊,當一塊內存被用完了就將還存活的對象一次複製到另外一塊內存上,而後把已經使用過的內存一次清理掉。不過所以內存縮小爲原來的一半,代價太高。
如今的商業虛擬機廣泛採用這種算法來回收新生代,將新生代分爲較大的一塊Eden空間和兩塊較小的Survivor空間,HotSpot默認其比例爲8:1:1,使用時,每次使用Eden加上其中一塊Survivor,回收時,將Eden和Survivor中還存活的對象一次性複製到另外一塊Survivor中,最後清理掉Eden和剛纔用過的Survivor區。
標記過程與「標記-清除」算法同樣,而後讓全部存活的對象都向同一端移動,而後直接清理端邊界之外的內存。
將Java堆分爲新生代和老年代,在新生代中每次垃圾回收都有大批對象死去,少許存活,可使用複製算法,而老年代中對象存活率高沒有額外的空間對它進行分配擔保,必須使用「標記-整理」或者「標記-清理」算法來回收。
在最近更新的JDK9中,JVM默認的垃圾收集器切換成了G1,那麼G1有什麼特色呢?總結如下最大的四個特色
《深刻理解Java虛擬機》P61-P84