一、回收堆區
垃圾回收器在堆進行垃圾回收前,首先要判斷這些物件那些還存活,那些已經“死去”,判斷物件是否已“死”有如下幾種演算法:
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相連接的參考鏈,那它將會被第一次標記并且進行一次篩選,
篩選的條件是此物件是否有必要執行?nalize()方法,
? 沒必要:沒有覆寫?nalize()方法 或 ?nalize()方法已經被呼叫過一次了
審判
如果這個物件被判定為有必要執行?nalize()方法,那么這個物件將會被放置在一個叫做F-Queue的佇列之中,并在稍后由一個虛擬機自動建立的、低優先級的Finalizer執行緒去執行它
如果物件在?nalize()中成功拯救自己:與參考鏈上的任何一個物件建立起關聯關系
那在第二次標記時它將會被移除出"即將回收"的集合,也就暫時逃脫死亡的命運了,
如果物件這時候還是沒有逃脫,那基本上它就是真的被回收了,
二、回收方法區
方法區(永久代)的垃圾回收主要收集兩部分內容:廢棄常量和無用類,
廢棄常量:
沒有任何一個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(如果存在的話)等所有部分的模式,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/356018.html
標籤:Java
下一篇:行程與執行緒
