本文主要爲《深刻理解Java虛擬機》第三章的讀書記錄筆記,同時伴有一些網絡上資料的總結。html
Java堆是JVM主要的內存管理區域,裏面存放着大量的對象實例和數組。在垃圾回收算法和垃圾收集器以前,首先要作的就是判斷哪些對象已經「死去」,須要進行回收即不可能再被任何途徑使用的對象。java
引用計數法是這樣:給對象中添加一個引用計數器,每當有一個地方使用它時,計數器值就加1。當引用失效時,計數器就減1。任什麼時候刻計數器爲0的對象就是不可能再被使用的。算法
如今主流的Java虛擬機都沒有使用引用計數法,最主要的緣由就是它很難解決對象之間互相循環引用的問題。數組
可達性分析的基本思路:經過一系列稱爲"GC Roots"的對象做爲起點,從這些節點開始向下搜索,若是從GC Roots到一個對象不可達,則證實此對象是不可用的,以下圖所示。瀏覽器
Java語言中,可做爲GC Roots的對象包括下面幾種:緩存
對於Java程序而言,對象基本都位於堆內存中,簡單來講GC Roots就是有被堆外區域引用的對象。bash
在JDK 1.2
之前的版本中,若一個對象不被任何變量引用,那麼程序就沒法再使用這個對象。也就是說,只有對象處於(reachable
)可達狀態,程序才能使用它。網絡
從JDK 1.2
版本開始,對象的引用被劃分爲4
種級別,從而使程序能更加靈活地控制對象的生命週期。這4
種級別由高到低依次爲:強引用、軟引用、弱引用和虛引用。多線程
強引用是使用最廣泛的引用,以下的方式就是強引用:併發
Object strongReference = new Object();
複製代碼
舉例來講,
public void test() {
Object strongReference = new Object();
// 省略其餘操做
}
複製代碼
strongReference = null
後。這個對象再也不被GC Roots可達,那麼這個對象在下次GC時就會被回收。class Obj {
pulic static Object strongReference = new Object();
}
複製代碼
若是對象只具備軟引用,則
// 強引用
String strongReference = new String("abc");
String str = new String("abc");
// 軟引用
SoftReference<String> softReference = new SoftReference<String>(str);
複製代碼
軟引用能夠和一個引用隊列(ReferenceQueue)聯合使用。若是軟引用的對象被垃圾回收,JVM就會把這個軟引用加入到與之關聯的引用隊列中。
ReferenceQueue<String> referenceQueue = new ReferenceQueue<>();
// 強引用
String str = new String("abc");
SoftReference<String> softReference = new SoftReference<>(str, referenceQueue);
// 消除強引用
str = null;
// Notify GC
System.gc();
System.out.println(softReference.get()); // abc
Reference<? extends String> reference = referenceQueue.poll();
System.out.println(reference); //null
複製代碼
注意:
也就是說,垃圾收集線程會在虛擬機拋出OutOfMemoryError以前回收軟引用對象,並且虛擬機會盡量優先回收長時間閒置不用的軟引用對象。對那些剛構建的或剛使用過的較新的軟對象會被虛擬機儘量保留。
應用場景:
瀏覽器的後退按鈕。按後退時,這個後退時顯示的網頁內容是從新進行請求仍是從緩存中取出呢?這就要看具體的實現策略了。
這時候就可使用軟引用,很好的解決了實際的問題:
// 獲取瀏覽器對象進行瀏覽
Browser browser = new Browser();
// 從後臺程序加載瀏覽頁面
BrowserPage page = browser.getPage();
// 將瀏覽完畢的頁面置爲軟引用
SoftReference softReference = new SoftReference(page);
// 消除強引用
page = null;
// 回退或者再次瀏覽此頁面時
if(softReference.get() != null) {
// 內存充足,尚未被回收器回收,直接獲取緩存
page = softReference.get();
} else {
// 內存不足,軟引用的對象已經回收
page = browser.getPage();
// 從新構建軟引用
softReference = new SoftReference(page);
}
複製代碼
相比較軟引用,只具備弱引用的對象擁有更短暫的生命週期。在垃圾回收器線程掃描它鎖管轄的內存區域的過程當中,一旦發現了只具備弱引用的對象,無論當前內存空間足夠與否,都會回收它的內存。不過,因爲垃圾回收器是一個優先級很低的線程,所以不必定會很快發現那些只具備弱引用的對象。
String str = new String("abc");
WeakReference<String> weakReference = new WeakReference<>(str);
// 消除強引用
str = null;
複製代碼
一樣,弱引用能夠和一個引用隊列(ReferenceQueue)聯合使用,若是弱引用的對象被垃圾回收,JVM就會把這個弱引用加入到與之關聯的引用隊列中。
ReferenceQueue<String> queue = new ReferenceQueue<>();
String str = new String("abc");
WeakReference<String> weakReference = new WeakReference<>(str, queue);
str = null;
System.gc();
try {
// 休息幾分鐘,等待上面的垃圾回收線程運行完成
Thread.sleep(6000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(weakReference.get()); // null
System.out.println(queue.poll()); // java.lang.ref.WeakReference@22a71081
複製代碼
虛引用顧名思義,就是形同虛設。與其餘幾種引用都不一樣,虛引用並不會決定對象的生命週期。若是一個對象僅持有虛引用,那麼它就和沒有任何引用同樣,在任什麼時候候均可能被垃圾回收器回收。
應用場景:
虛引用主要用來跟蹤對象被垃圾回收器回收的活動。 虛引用與軟引用和弱引用的一個區別在於:
虛引用必須和引用隊列(ReferenceQueue)聯合使用。當垃圾回收器準備回收一個對象時,若是發現它還有虛引用,就會在回收對象的內存以前,把這個虛引用加入到與之關聯的引用隊列中。
String str = new String("abc");
ReferenceQueue queue = new ReferenceQueue();
// 建立虛引用,要求必須與一個引用隊列關聯
PhantomReference pr = new PhantomReference(str, queue);
複製代碼
程序能夠經過判斷引用隊列中是否已經加入了虛引用,來了解被引用的對象是否將要進行垃圾回收。若是程序發現某個虛引用已經被加入到引用隊列,那麼就能夠在所引用的對象的內存被回收以前採起必要的行動。
標記-清除算法分爲「標記」和「清除」兩個階段,執行過程以下圖所示:
標記-清除算法主要有兩個不足:
複製算法的大體思路以下,其執行過程以下圖所示:
複製算法的代價就是將內存縮小爲原來的一半。
如今的商業虛擬機都是採用複製算法來回收新生代。
標記-整理算法分爲「標記」和「整理」兩個階段,執行過程以下圖所示:
分代收集算法就是降Java堆分爲新生代和老年代,根據其各自的特色採用最適當的收集算法。
JVM垃圾收集器發展歷程大體能夠分爲如下四個階段: Serial(串行)收集器 -> Parallel(並行)收集器 -> CMS(併發)收集器 -> G1(併發)收集器
下圖展現了7種做用域不一樣分代的收集器,若是兩個收集器之間存在連續,就說明它們能夠搭配使用。下面逐一介紹這些收集器的特性、基本原理和使用場景。
Serial類收集器是一個單線程的收集器:
它只會用單個收集線程去進行垃圾回收的工做
它在進行垃圾收集的時候會「Stop The World」暫停其餘全部的工做表線程,直到它收集結束
Serial收集器採起複製算法在新生代進行單線程的回收工做
Serial Old收集器採起標記-整理算法在老年代進行單線程的回收工做
Parallel類收集器就是Serial收集器的多線程版本:
Parallel Scavenge收集器還有一個開關參數-XX: UseAdaptiveSizePolicy,打開這個開關後就不用手動指定新生代的大小(-Xmn),Eden與Survivor區的比例(-XX:SurvivorRatio)等細節參數了,JVM會動態調整這些參數已提供最合適的停頓時間或者最大吞吐量。
CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時間爲目標的收集器。它是一個基於標記-清除算法實現的,運做過程分爲4個步驟:
初始標記(CMS initial mark): 須要「Stop The World」,僅僅只是標記下GC Roots能直接關聯到的對象,速度很快
併發標記(CMS concurrent mark): CMS線程與應用線程一塊兒併發執行,從GC Roots開始對堆中對象進行可達性分析,找出存活對象,耗時較長
從新標記(CMS remark):從新標記就是爲了修正併發標記期間因用戶線程繼續運做而致使標記產生變更的那一部分對象的標記記錄,能夠多線程並行
併發清除(CMS concurrent sweep):CMS線程與應用線程一塊兒併發執行,進行垃圾清除
CMS收集器優勢:併發收集、低停頓
CMS的三個明顯的缺點:
同優秀的CMS同樣,G1也是關注最小停頓時間的垃圾回收器,也一樣適合大尺寸堆內存,官方也推薦用G1來代替選擇CMS。
G1以前的JVM堆內存模型,堆被分爲新生代,老年代,永久代(1.8以前,1.8以後是元空間),新生代中又分爲Eden和兩個Survivor區。
G1收集器的堆內存模型,堆被分爲不少個大小連續的區域(Region),Region的大小能夠經過-XX: G1HeapRegionSize參數指定,大小區間爲[1M,32M]。
每一個Region被標記了E、S、O和H,這些區域在邏輯上被映射爲Eden,Survivor,老年代和巨型區(Humongous Region)。巨型區域是爲了存儲超過50%標準region大小的巨型對象。
G1能夠在新生代和老年代使用,而CMS只能在老年代使用。
G1是複製+標記-整理算法,CMS是標記清除算法。
G1收集器的工做流程大體分爲以下幾個步驟:
G1提供了兩種GC模式,Young GC和Mixed GC,兩種都是徹底Stop The World的
當愈來愈多的對象晉升到老年代old region時,爲了不堆內存被耗盡,虛擬機會觸發一次mixed gc,該算法並非一個old gc,除了回收整個young region,還會回收一部分的old region。這裏須要注意:是一部分老年代,而不是所有老年代,能夠選擇哪些old region進行收集,從而能夠對垃圾回收的耗時時間進行控制
G1沒有fullGC概念,須要fullGC時,調用serialOldGC進行全堆掃描(包括eden、survivor、o、perm)。