前言:要弄清Java虛擬機GC的整個過程,就得弄明白Java虛擬機用什麼來進行GC?Java虛擬機在哪裏GC?何時GC?GC什麼?算法
GC(Garbage Collection)垃圾收集,JVM一個很是重要的功能。本文將圍繞着JVM的GC這個動做展開,來過一遍GC的整個運做過程。post
JVM是GC的發起者,準確說是VMThread是GC的發起者,那用什麼來進行GC呢?很顯然,用到的是垃圾收集器來進行GC的。而垃圾收集器,能夠根據堆中的分代,分爲不一樣類型的垃圾收集器。this
新生代(Young generation)spa
老年代(Tenured generation)線程
G1是一個特殊的垃圾收集器,既能夠做爲新生代的垃圾收集器,也能夠做爲老年代的垃圾收集器。3d
更加詳細的垃圾收集器的知識,能夠閱讀《深刻理解JVM》這本書。cdn
JVM的GC動做只在兩個地方回收————堆和方法區。(JDK8的metaspace的GC,這裏暫不討論。若是還有其餘內存區域發生垃圾回收,請指正)對象
在堆取進行GCblog
首先,得了解堆是什麼,有什麼做用?言簡意賅——總結Java內存區域和常量池隊列
這裏須要知道,JVM的GC採用了分代思想,因此堆被分紅了新生代和老年代,而新生代又被細分爲Eden區和From survivor和To survivor區。當類被JVM加載後,Java應用程序運行而後某個對象被new,這個對象就會被分配到堆中的新生代的Eden區(對象優先被分配到Eden區),若是Eden區域沒有足夠的內存來分配給該對象,就會觸發minor GC來清除已經「死亡」的對象,這樣才能將新清出的內存分配給該對象,通過GC都還存活的對象,會被移至From survivor區中。因爲新生代採用的複製算法,Eden區存活對象和From survivor區的存活對象將被複制到To survivor區中。在To survivor區中的對象,每通過一次GC,對象中的「年齡計數器」就會加1,若是超過了晉升爲老年代的年齡閾值時(默認爲15)對象就會晉升到老年代中。
因爲老年代裏存放的都是大對象、存活時間較久的對象,所以老年代通常都是用標記-整理算法或標記-清除算法。當老年代對象無法再分配內存時,會觸發一次Major GC(Full GC),用於回收那些已經「死亡」的對象。
下圖爲堆中分代
在方法區中進行GC
在堆中進行GC通常能夠回收70%~95%的空間,相比在方法區中進行GC效率是很是低的。可是效率低不表明不進行GC。永久代的垃圾收集主要回收兩部份內容:廢棄常量和無用的類。
在上文中,已經講到了JVM何時進行GC。就是在Eden區沒有足夠內存分配給對象的時候進行Minor GC,在老年代無法再分配內存給大對象以及「老」對象的時候進行的Major GC(full GC)。這裏談一談Minor GC和Major GC:
這裏,首先得了解對象在什麼狀況下會被進行GC?
是「真正死亡」的對象嗎?
那麼,對象怎麼才能被斷定爲「真正死亡」呢?JVM是經過可達性分析來進行分析的。可達性分析就是經過從GC Roots爲起點,判斷是否有一個引用鏈與被判斷的對象相連,若是相連這表明這個對象還「活着」,是可用的。然而,一個對象被真正斷定爲「死亡」,須要進行兩次標記(被標記爲可回收對象),而後在下一次JVM進行GC的時候,才被真正的回收掉。若是一個對象沒有與GC Roots的引用鏈相鏈接,也並無被真正斷定爲「死亡」,而是進行第一次標記,而後在第二次標記以前會進行「自救」過程。所謂的「自救」就是在第二次被標記以前,須要從新與引用鏈相鏈接。在第一次被標記後,對象會進行篩選,篩選的條件爲是否有必要進行執行finalize()方法。若是沒有必要執行finalize()方法,則就等待第二次被標記;若是有必要執行finalize()方法,則會先將對象放置在一個叫F-Queue的隊列中,而後等待虛擬機經過Finalizer線程去觸發對象的finalize()方法,而後在finalize()方法裏面,對象就開始自救的過程。對象自救能夠經過把this賦值給某個類變量或者對象的成員變量,就實現了和引用鏈從新鏈接的目的——自救成功。所以在第二次標記的時候就會把對象從F-Queue隊列中移除,而後虛擬機會在F-Queue中進行第二次小規模的標記,而後就等待下一次GC的來臨。
結論:
因此,從上面分析的結果來看,JVM進行GC的對象,就是沒有和引用鏈上相連的而且通過第一次標記,沒有「自救」成功的對象。