本文從JVM如何斷定對象是否須要回收開始分析,再到JVM的幾種垃圾回收思想如何產生,最後再來介紹JVM經典的7種垃圾回收器的特色(不包含ZGC);算法
JVM根據對象存活週期不一樣將heap劃分紅了新生代、老年代、永久代(方法區&元空間)。
有個問題,JVM是先有的分代思想而後根據不一樣的代發展不一樣的垃圾回收思想,仍是先有的垃圾回收思想才劃分不一樣的代?
多線程
JAVA與C有個很顯著的不一樣,就是JAVA不須要手動歸還內存,徹底由GC自動管理內存回收。那麼GC是如何判斷對象是否須要回收的呢?併發
引用計數法性能
引用計數法是指在對象中添加一個引用計數器,若是被其餘對象引用則計數器+1,引用失效時-1。
優勢:實現簡單,判斷效率也很高;
缺點:存在對象循環引用問題,因此在主流的虛擬機中並無採用引用計數器。
對象A持有對象B的引用,對象B持有對象A的引用,除此以外在無其餘對象引用A和B,GC沒法回收這樣的對象.
線程
可達性分析3d
在主流商用語言(JAVA/C#/Lisp)都是使用可達性分析算法來斷定對象是否存活。主要思想就是經過一系列被稱爲GC Roots
的對象做爲起始點開始先下搜索,走過的路徑稱爲引用鏈,若是某個對象沒有任何一條到達GC Roots
對象的引用鏈則表明此對象可回收的。
JAVA中能夠被稱爲GC Roots
對象:code
- 虛擬機棧(棧幀中的本地變量表)中引用的對象;
- 方法區中的類靜態屬性引用的對象;
- 方法區中常量引用的對象;
- 本地方法棧中JNI(即通常說的Native方法)中引用的對象;
GC Roots
沒法到達的對象並非必定會被回收,一個對象至少要被標記兩次纔會真正死亡。
cdn
GC Roots
時會被第一次標記,並進行一次篩選,篩選的條件是該對象是否有必要執行finalize()
。finalize()
方法finalize()
方法finalize()
方法;finalize()
方法裏關聯上任何一條引用鏈,則會被移出即將要回收的集合,不然該對象真正「死亡」。在JVM知道那些對象是可回收的後,須要開始真正的回收對象了。JVM在發展的過程當中出現了幾種經典的回收思想,這裏不討論每種算法具體如何實現(由於我也不瞭解...)。對象
複製算法
爲了解決效率問題,出現了一種複製的算法,一開始是將內存按1:1劃分紅兩塊,每次只在其中一塊內存上分配對象,當觸發垃圾回收時將存活的對象所有複製到另外一塊的內存上,而後把已經使用過的那快內存清空掉。這樣既解決了效率問題也解決了內存碎片化的問題。但同時也帶來了空間浪費的缺點:每次只能使用50%的空間。blog
實際狀況並非每次回收時一塊Survivor都能裝下全部存活對象,那這時就會經過「空間分配擔保」的機制直接晉升到老年代。
標記-整理算法
因爲老年代的對象都是長期存活,因此複製算法並不適用老年代,所以又提出了「標記-整理」算法,標記過程與「標記-清除」算法同樣,只是後續並非直接清除對象而是先將全部存活對象都向一端移動,而後直接清理掉邊界之外的內存。
分代收集算法
當前主流商用垃圾回收器都是採用的「分代收集算法」,這個算法並無什麼新的思想只是根據對象存活週期的不一樣將內存劃分紅不一樣的代而後採用不一樣的回收算法。
黃色表明只處理新生代的GC,藍色表明只處理老年代GC,各GC之間的連線表明能夠搭配使用。G1能夠獨立回收整個head;
在介紹這些收集器各自的特性以前,讓咱們先來明確一個觀點:雖然咱們會對各個收集器進行比較,但並不是爲了挑選一個最好的收集器出來,雖然垃圾收集器的技術在不斷進步,但直到如今尚未最好的收集器出現,更加不存在「萬能」的收集器,因此咱們選擇的只是對具體應用最合適的收集器。若是有一種放之四海皆準、任何場景下都適用的完美收集器存在,HotSpot虛擬機徹底不必實現那麼多種不一樣的收集器了(摘選自《深刻理解Java虛擬機(第2版)》)。
這裏說明一下"並行"和"併發"的概念。
- 並行(Parallel):多條垃圾收集線程並行工做,而此時用戶線程仍處於等待狀態。
- 併發(Concurrent):垃圾收集線程與用戶線程同時執行(不必定是並行有多是交替執行),多核CPU的狀況下不一樣的線程在不一樣的CPU上同時執行。
Serial
Serial收集器是最基本歷史最悠久的收集器,JDK1.3.1以前是新生代惟一的選擇。Serial是一個單線程收集器,這裏的「單線程」並非指一個CPU或一條線程而是Serial在垃圾收集時必須暫停其餘工做線程(Stop The World)也就是俗稱的「STW」。
ParNew
ParNew收集器是Serial收集器的多線程版本,除使用多條線程進行垃圾收集以外,其他行爲包括控制參數、收集算法、Stop The World、對象分配規則、回收策略等都與Serial收集器徹底同樣,在實現上,這兩種收集器也共用了至關多的代碼。
CMS
搭配使用,多核CPU狀況下能有效利用系統資源。
Parallel Scavenge
Parallel Scavenge收集器是一個並行的多線程年輕代收集器,其餘收集器關心如何縮短垃圾收集的時間而它關注的是如何控制系統運行的吞吐量(吞吐量(吞吐量 = 代碼運行時間 / (代碼運行時間 + 垃圾收集時間))
)。高吞吐量能夠高效率的利用CPU時間,儘快完成運算任務,只要適合在後臺運算而不須要太多交互的任務。
Serial Old
Serial的老年代版本,它也是一款使用"標記-整理"算法的單線程的垃圾收集器,優劣和Serial同樣。有兩大用途:
Concurrent Mode Failure
狀況下老年代預備方案
Parllel Old
Parallel Old收集器是Parallel Scavenge收集器的老年代版本,使用"標記-整理"算法。JDK1.6才提供,在此以前Parallel Scavenge只能和單線程的Serial Old搭配使用,因爲老年代的Serial Old在服務端拖累又不能有效利用多核CPU的處理能力,致使Parallel Scavenge的高吞吐名副其實。直到Parllel Old的出現「吞吐量優先」的收集器纔有了用武之地,任何注重吞吐量以及CPU資源敏感的場合,均可以優先考慮Parallel Scavenge加Parallel Old收集器一塊兒配合使用
CMS
真正意義上的一款具備劃時代意義的垃圾收集器,基於「標記-清除」算法實現,關注點在獲取最短停頓時間爲目標,大量運用在B/S系統的服務端上。
整個回收過程分爲四個步驟:
標記
GC Roots
能直接關聯到的對象,速度很快。須要STW
標記
GC Roots
找到全部能關聯到的對象
由於
併發標記
是和用戶線程併發的因此在標記的過程當中會產生新的對象,因此要從新標記。須要STW
併發清除前面全部標記的對象。
G1
G1全稱「Garbage First」垃圾收集器直至JDK7,Sun公司才認爲G1達到足夠成熟的商用程度,目標是在將來能夠替換掉CMS。以前的GC都只負責整個新生代/老年代,而G1能夠獨立負責整個Heap,G1是將整個Heap劃分紅多個大小相等的Region,邏輯上仍保留分代的概念,但已不是物理分隔了,它們都是一部分不須要連續的Region集合。
G1有如下特色:
指定一個長度爲M毫秒的時間段內,消耗在垃圾收集上的時間不超過N毫秒
)。G1對每個Region的垃圾堆積的價值大小維護了一個優先列表,每次根據容許的收集時間,優先回收價值大的Region(這就是Garbage First名稱的由來),保證了有限的時間內獲取儘量高的收集效率。G1收集器的大概步驟:
前三個步驟與CMS運做過程大體類似,