一、回收堆區
垃圾回收器在堆進行垃圾回收前,首先要判斷這些對象那些還存活,那些已經“死去”。判斷對象是否已“死”有如下幾種算法:
1.引用計數法
給對象增加一個引用計數器,每當有一個地方引用它時,計數器就+1;
當引用失效時,計數器就-1;
任何時刻計數器為0的對象就是不能再被使用的,即對象已“死”。
引用計數法實現簡單,判定效率也比較高,在大部分情況下都是一個比較好的算法。
但是,在主流的JVM中沒有選用引用計數法來管理內存,最主要的原因是引用計數法無法解決對象的循環引用問題。
2. 可達性分析算法
在上面講了,Java並不采用引用計數法來判斷對象是否已“死”,而采用“可達性分析”來判斷對象是否存活。
通過一系列稱為“GC Roots”的對象作為起始點,從這些節點開始向下搜索,搜索走過的路徑稱為“引用鏈”,當一個對象到 GC Roots 沒有任何的引用鏈相連時(從 GC Roots 到這個對象不可達)時,證明此對象不可用。以下圖為例:

在Java語言中,可作為GC Roots的對象包含以下幾種:
- 虛擬機棧(棧幀中的本地變量表)中引用的對象。
- 方法區中靜態屬性引用的對象
- 方法區中常量引用的對象
- 本地方法棧中(Native方法)引用的對象
引用
在JDK1.2以前,Java中引用的定義很傳統: 如果引用類型的數據中存儲的數值代表的是另一塊內存的起始地址,就稱這塊內存代表着一個引用。
這種定義有些狹隘,一個對象在這種定義下只有被引用或者沒有被引用兩種狀態。
在JDK1.2之后,Java對引用的概念做了擴充,將引用分為強引用(Strong Reference)、軟引用(Soft Reference)、弱引用(Weak Reference)和虛引用(Phantom Reference)四種,這四種引用的強度依次遞減。
1.強引用
類似於"Object obj = new Object()"這類的引用,只要強引用還存在,垃圾回收器永遠不會回收掉被引用的對象實例。
當內存空 間不足,Java虛擬機寧願拋出OutOfMemoryError錯誤,使程序異常終止,也不會靠隨意回收具有強引用的對象來解決內存不足問題。
2.軟引用
如果一個對象只具有軟引用,那就類似於可有可無的生活用品。
如果內存空間足夠,垃圾回收器就不會回收它,如果內存空間不足了,就會回收這些對象的內存。
軟引用可用來實現內存敏感的高速緩存。
舉例:查看網頁,可能后退,那么剛才的網頁要不要一直存儲,一直存儲就是強引用,不存儲就是回收,那么折中一下,於是產生了弱引用,當內存空間足夠時,就不回收,不足時,再回收。這個根據內存敏感程度而變化而決定是否緩存,就是內存敏感的高速緩存。
3.弱引用
對象擁有更短暫的生命周期。
在gc線程掃描它所管轄的內存區域的過程中,一旦發現了只具有弱引用的對象,不管當前內存空間是否充足,都會回收它的內存。
由於gc是一個優先級很低的線程,因此不一定會很快發現那些只具有弱引用的對象。
4.虛引用
就是形同虛設,與其他幾種引用都不同,虛引用並不會決定對象的生命周期。
如果一個對象僅持有虛引用,那么它就和沒有任何引用一樣,在任何時候都可能被垃圾回收器回收。
作用: 虛引用主要用來跟蹤對象被垃圾回收的活動
區別: 虛引用與軟引用和弱引用的一個區別在於:虛引用必須和引用隊列 (ReferenceQueue)聯合使用。
當垃圾回收器准備回收一個對象時,如果發現它還有虛引用,就會在回收對象的內存之前,把這個虛引用加入到與之關聯的引用隊列中。程序可以通過判斷引用隊列中是否已經加入了虛引用,來了解被引用的對象是否將要被垃圾回收。
如果程序發現某個虛引用已經被加入到引用隊列,那么就可以在所引用的對象的內存被回收之前采取必要的行動。
3.真正判決
要宣告一個對象的真正死亡,至少要經歷兩次標記過程
如果對象在進行可達性分析之后發現沒有與GC Roots相連接的引用鏈,那它將會被第一次標記並且進行一次篩選。
篩選的條件是此對象是否有必要執行finalize()方法。
沒必要:沒有覆蓋finalize()方法 或 finalize()方法已經被調用過一次了
審判
如果這個對象被判定為有必要執行finalize()方法,那么這個對象將會被放置在一個叫做F-Queue的隊列之中,並在稍后由一個虛擬機自動建立的、低優先級的Finalizer線程去執行它
如果對象在finalize()中成功拯救自己:與引用鏈上的任何一個對象建立起關聯關系
那在第二次標記時它將會被移除出"即將回收"的集合,也就暫時逃脫死亡的命運了。
如果對象這時候還是沒有逃脫,那基本上它就是真的被回收了。
二、回收方法區
方法區(永久代)的垃圾回收主要收集兩部分內容:廢棄常量和無用類。
廢棄常量:
沒有任何一個String對象引用常量池中的"abc"常量,也沒有其他地方引用這個字面量,如果此時發生GC並且有必要的話,這個"abc"常量會被系統清理出常量池。
常量池中的其他類(接口)、方法、字段的符號引用也與此類似。
無用類
1.該類的所有實例都已經被回收(即在Java堆中不存在任何該類的實例)
2.加載該類的ClassLoader已被回收
3.該類對應的Class對象沒有任何其他地方被引用,無法在任何地方通過反射訪問該類的方法
三、垃圾回收算法
1.標記-清除算法
首先標記出所有需要回收的對象,在標記完成后統一回收所有被標記的對象
“標記-清除”算法的不足主要有兩個
- 效率問題:標記和清除這兩個過程的效率都不高
- 空間問題:標記清除后會產生大量不連續的內存碎片,空間碎片太多可能會導致以后在程序運行中需要分配較大對象時,無法找到足夠連續內存而不得不提前觸發另一次垃圾收集。

2.復制算法(新生代回收算法)
它將可用內存按容量划分為大小相等的兩塊,每次只使用其中一塊。
當這塊內存需要進行垃圾回收時,會將此區域還存活着的對象復制到另一塊上面,然后再把已經使用過的內存區域一次清理掉。
因為復制過去后,另一邊的內存肯定是連續的了,此時再把使用過得內存區域清理,從而達到了整理的效果。
也就是伊甸園區移動到survive0和survive1區的算法。
但是,伊甸園區的對象都是朝生夕死的,所以並不需要1:1的空間,所以出現了8:1:1的默認比例

3.標記整理算法(老年代回收算法)
復制收集算法在對象存活率較高時會進行比較多的復制操作,效率會變低。因此在老年代一般不能使用復制算法。
而是采用標記整理算法
標記過程仍與“標記-清除”過程一致,但后續步驟不是直接對可回收對象進行清理,而是讓所有存活對象向一端移動,然后直接清理掉除存活對象以外的內存。流程圖如下:

4.分代收集算法
就是將堆區分開,不同的位置采用不同的算法
在新生代中,每次垃圾回收都有大批對象死去,只有少量存活,因此我們采用復制算法;
而老年代中對象存活率高,就必須采用"標記-清理"或者"標記-整理"算法。
四 .Minor GC、Major GC、Full GC的區別?
Minor GC 又稱為新生代GC 指的是發生在新生代的垃圾回收操作(包括Eden區和Survivor區)。
當年輕代內存空間被用完時,就會觸發垃圾回收。這個垃圾回收叫做Minor GC。
Major GC通常是跟full GC是等價的,收集整個GC堆。
但因為HotSpot VM發展了這么多年,外界對各種名詞的解讀已經完全混亂了
Full GC定義是相對明確的,就是針對整個新生代、老生代、元空間(metaspace,java8以上版本取代perm gen)的全局范圍的GC。
針對HotSpot VM GC來看
它里面的GC其實准確分類只有兩大種:
Partial GC:並不收集整個GC堆的模式
- Young GC:只收集年輕代的GC
- Old GC:只收集老年代的GC。只有CMS的concurrent collection是這個模式
- Mixed GC:收集整個年輕代以及老年代的GC。只有G1有這個模式
Full GC:收集整個堆,包括young gen、old gen、perm gen(如果存在的話)等所有部分的模式。
