作為 Java 開發人員, 因為 JVM 的存在, Java 開發人員不需要像 C 或者 C++開發人員那樣需要手動申請記憶體、釋放記憶體,這些資源申請、垃圾回收的操作,JVM 底層直接幫助我們全干了,
參考自: 微信"菜鳥飛呀飛", 微信號"tiantang-2013"
垃圾回收
在 JVM 中,虛擬機規范將一大塊記憶體細分為了很多不同的小區域,而 JVM 要想進行垃圾回收,首先得知道垃圾回收要回收的是哪些區域中的物件, 下面這張圖相信大家已經見過很多次了,它是虛擬機規范中一張經典的 JVM 記憶體結構圖,圖中的運行時資料區包含 5 個部分:堆區、方法區、程式計數器、虛擬機堆疊、本地方法堆疊,其中程式計數器、虛擬機堆疊、本地方法堆疊是每個執行緒私有的區域,它們隨著執行緒的創建而生,隨著執行緒的死亡而消失,因此這部磁區域不需要 JVM 單獨對它們進行垃圾回收,而堆區和方法區中存放的是物件、常量池、類資訊等資料,這些資料是所有執行緒共享的,它們的生命周期不會伴隨著執行緒的生而生,死而死,它們需要 JVM 單獨進行垃圾回收,
請注意以上段落中 加黑粗體 的關鍵詞!!!

知道了 JVM 中垃圾回收的目標區域,但是要對這些區域中的垃圾進行回收,JVM 首先得知道哪些物件是垃圾,而判斷一個物件是否是垃圾,通常有兩種演算法:參考計數演算法、可達性分析演算法,下面將依次介紹這兩種演算法,
1.參考計數演算法
采用參考計數演算法來判斷一個物件是否存活,其原理是:為每一個物件分配一個計數器,當這個物件被另一個物件參考時,這個計數器就加一;當被另一個物件取消參考時,計數器就減一,當這個計數器的值為零時,就表示當前物件沒有被任何物件所參考,那么這個物件就可以被垃圾回收器進行回收了,
參考技術演算法實作起來十分簡單,也十分高效,但是它有個致命的缺點,就是無法解決回圈參考的問題,例如如下示例代碼:
public class ReferenceCountTest { private ReferenceCountTest reference; public static void main(String[] args) { ReferenceCountTest objA = new ReferenceCountTest(); ReferenceCountTest objB = new ReferenceCountTest(); objA.reference = objB; objB.reference = objA; objA = null; objB = null; } }
示例代碼中,變數 objA 和 objB 相互之間回圈參考,如果采用參考計數演算法來判斷物件是否存活的話,即使我們將 objA 和 objB 設定為 null 后,由于它們各自的參考計數器均為 1,垃圾回收器會認為 objA 和 objB 還有人在使用,因此不會回收 objA 和 objB,
正是因為參考計數演算法無法解決回圈參考的問題,因此目前 Java 中的垃圾回收器均沒有使用參考計數演算法來判斷一個物件是否存活,而是采用下面即將介紹的可達性分析演算法,
2.可達性分析演算法
可達性分析演算法的實作思路是:將一系列被稱之為“GC Roots”的根物件作為起始節點,從這個根節點出發,通過參考關系向下尋找它可以到達的物件,尋找程序中經過的路線稱之為參考鏈,一個系統中可以有多個根節點,也就是說 GC Roots 是一個節點的集合,如果一個物件無法通過任何一個 GC Roots 根節點找到,即 0 條參考鏈,那么這個物件就不是存活物件了,后面在進行垃圾回收時,可以被垃圾收集器回收,

注意: 如果要使用可達性分析演算法來進行垃圾標記,那么就必須保證在整個可達性分析程序當中,系統必須處于一致性快照當中,什么意思呢?就是在可達性分析程序中,不能有用戶執行緒更新物件間的參考關系,否則可達性分析演算法的分析結果的準確性就無法保證了,因此在可達性分析演算法的作業當中,會暫停所有的用戶執行緒,也就是”Stop The World“,簡稱 STW,
2.1GC Roots
在可達性分析演算法中提到了 GC Roots 這個概念,那么在 Java 中,有哪些物件可以被作為 GC Roots 呢?分別有如下幾種情況,
1.虛擬機堆疊中每個堆疊幀中區域變數表里面的參考物件,如方法的入參,區域變數等,
2.本地方法堆疊中的參考物件,
3.方法區中類的靜態屬性參考的物件,
4.方法區中常量池參考的物件,如:字串常量池參考的物件,
5.被關鍵字 synchronized 鎖住的物件,
6.Java 虛擬機內部參考的物件,如:一些常駐的例外物件(NullPointerException、OutOfMemoryError),基本資料型別的 Class 物件,系統類加載器等,
7.反應 Java 虛擬機內部情況的 JMXBean、JVMTI 中注冊的回呼、本地代碼快取等,
8.除了這些固定的 GC Roots 外,根據用戶所選的垃圾收集器以及當前回收的記憶體區域不同,還可以有其他物件”臨時性“地加入,共同構成完整的 GC Roots 集合,比如:分代收集器和區域回收,
總結
這里主要介紹了 JVM 垃圾回收的作用區域,以及如何判斷一個物件是否是垃圾,通常可以通過參考計數法和可達性分析演算法來判斷一個物件是否是垃圾,但是在目前 JVM 的垃圾收集器中,采用的都是可達性分析演算法,因為參考計數法無法解決回圈依賴的問題,最后列舉了在可達性分析演算法里,Java 中哪些物件可以作為 GC Roots,
垃圾回收通常會分為兩個階段:垃圾標記階段和垃圾清除階段,而參考計數演算法和可達性分析演算法作用的是垃圾標記階段, 后面的文章將會分享垃圾清除階段的相關演算法,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/161007.html
標籤:Java
