作為 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。
垃圾回收通常會分為兩個階段:垃圾標記階段和垃圾清除階段。而引用計數算法和可達性分析算法作用的是垃圾標記階段, 后面的文章將會分享垃圾清除階段的相關算法。