寫在前面
本文作為閱讀了周志明作者的 <<深入了解Java虛拟機>> 的讀書筆記。由于個人了解有限,本文摘抄的内容可能比較片面,強烈建議入手本書!還有比較遺憾的一件事是這部分的官方文檔我不知道去哪裡能夠找到,畢竟作者使用的是6,而我使用的是
JDK
8 ;如果能夠結合文檔來看的話,應該能消除更多疑惑吧。但我從這篇文檔中擷取到了一點資訊。文中的圖檔均參考書中的圖繪制而成。
JDK
對象已死?
在收集對象之前,需要知道對象哪些對象已經不可能在被使用,這需要一點小技巧。
引用計數算法
描述:給對象中添加一個引用計數器,每當有一個地方引用它時,計數器值就加1;當引用失效時,計數器值就減1;任何時刻計數器都為 0 的對象就是不可能再被使用的。
引用計數算法的實作簡單,判定效率也很高,但它很難解決對象之間的互相循環引用的問題。

考慮圖中這種情況,A,B 兩個對象互相引用,除此之外,這兩個對象再無任何引用,實際上這兩個對象已經不可能再被通路,但是它們因為互相引用着對方,導緻它們的引用計數都不為0,于是引用計數算法無法通知 GC 收集器回收它們。
我在文章開頭所提到的那篇文檔中找到這樣的一句話:
對象最終将成為未引用對象,并且它們所占用的存儲可以被其他對象回收使用。收集器的基本操作是周遊對象圖,查找所有可到達的對象并将其儲存,同時識别所有不可達的對象并恢複其存儲。周遊每個集合的整個對象圖将是非常昂貴的,是以已采用了多種技術來使集合更有效。
我們至少能從這段話中得到的資訊是,
GC
是以圖周遊的形式去查找所有可達對象的。
根搜尋算法
描述:通過一系列的名為 “
GC Roots
" 的對象作為起始點,從這些節點開始向下搜尋,搜尋所走過的路徑稱為引用鍊。當一個對象到
GC Roots
沒有任何引用鍊相連(圖論的描述就是從
GC Roots
到這個對象不可達)時,則證明此對象是不可用的。
JAVA 語言裡,
GC Roots
的對象包括下面幾種:
- 虛拟機棧(棧幀中的本地變量表)中的引用的對象
- 方法區中的類靜态屬性引用的對象
- 方法區中的常量引用的對象
- 本地方法棧中
( Native 方法) 的引用的對象JNI
方法區回收
書中詳細的介紹了這一塊,但值得一提的是 方法區(或者
HotSpot
虛拟機中的永久代)在 JDK 8 中已經被移除。但重新引入了一個新的本地記憶體區塊-類中繼資料。
Java類在中具有内部表示形式,被稱為類中繼資料。在
Java Hotspot VM
的早期版本中,類中繼資料是在所謂的永久生成中配置設定的。在
Java Hotspot VM
中,永久代已删除,并且類中繼資料已配置設定在本機記憶體中。預設情況下,可用于類中繼資料的本地記憶體數量是無限的。使用該選項
JDK 8
對用于類中繼資料的本機記憶體量設定上限。
MaxMetaspaceSize
顯式管理用于中繼資料的空間。從作業系統請求空間,然後将其分成多個塊。類加載器從其塊配置設定中繼資料空間(塊綁定到特定的類加載器)。當為類加載器解除安裝類時,其塊将被回收以重新使用或傳回給OS。中繼資料使用配置設定的空間
Java Hotspot VM
,而不是
mmap
。
malloc
書中有提到如何判定一個類是否是 “無用的類”,類需要同時滿足下面 3 個條件才能算是 “無用的類”:
- 該類所有的執行個體都已經被回收,也就是 Java 堆中不存在該類的任何執行個體。
- 加載該類的
已經被回收。ClassLoader
- 該類對應的
對象沒有在任何地方被引用,無法在任何地方通過反射通路該類的方法。java.lang.Class
垃圾收集算法
垃圾收集算法的實作涉及大量的程式細節,這裡僅僅介紹算法的思想。
标記-清除算法
描述:算法分為 ”标記“ 和 ”清除“(
Mark-Sweep
) 兩個階段:首先标記出所有需要回收的對象,在标記完成後統一回收掉所有被标記的對象。
缺點:
- 效率問題;标記和清除過程的效率都不高;
- 空間問題:空間碎片太多,當程式需要配置設定較大對象時而無法找到足夠的連續記憶體而不得不提前觸發另一次垃圾收集動作。
複制算法
目的:為了解決效率問題
描述:将可用記憶體分為大小相等的兩塊,每次使用其中的一塊。當這一塊的記憶體用完了,就将還存活着的對象複制到另外一塊上面,然後再把已使用過的記憶體空間一次清理掉。
缺點:可用記憶體縮小為原來的一半
标記-整理算法
複制收集算法在對象存活率較高時,就要執行較多的複制操作,效率将會變低。
描述:标記過程仍然與 “标記-清除” 算法一樣,但後續步驟不是直接對可回收對象進行清理,而是讓所有存活的對象都向一端移動,然後直接清理掉端邊界以外的記憶體。
分代收集算法
分代收集算法是一種組合算法,是根據弱代假設來的;弱代假設則是根據觀察大量應用程式,發現大多數對象隻存活一小段時間的這一現象,而得出來的一個假設。
描述:根據對象的存活周期的不同将記憶體劃分為幾塊。一般是把 Java 堆分為新生代和老年代,這樣就可以根據各個年代的特點采用最适當的收集算法。