先說一些題外話,Java虛擬機在執行Java程序的過程中會把它所管理的內存划分為若干個不同的數據區,這些區分為線程私有區和線程共享區
1、線程私有區
a、程序計數器
記錄正在執行的虛擬機字節碼指令地址。此區域是是唯一一個在java虛擬機規范中沒有規定任何OutOfMemoryError情況的區域。
b、Java虛擬機棧
描述的是Java方法執行的內存模型,每個方法在執行的同時會創建一個棧幀
c、本地方法棧
它與虛擬機棧發揮的作用是類似的,它們之間的區別不過是虛擬機棧為虛擬機執行java方法(也就是字節碼)服務,而本地方法棧則為虛擬機使用的Native方法服務。
2、線程共享區
a、Java堆
被所有線程共享的一塊內存區域,也是Java虛擬機所管理的內存中最大的一塊。
b、方法區
用於存儲已被虛擬機加載的類信息、常量、靜態變量、即時編輯器編譯后的代碼等數據,雖然Java虛擬機規范把方法區描述為堆的一個邏輯部分,但是它卻有一個別名Non-Heap(非堆)
下面開始說正題
目前虛擬機基本都是采用可達性算法,為什么不采用引用計數算法呢?下面就說說引用計數法是如何統計所有對象的引用計數的,再對比分析可達性算法是如何解決引用技術算法的不足。先簡單說說這兩個算法:
1、引用計數法(reference-counting):每個對象都有一個引用計數器,當對象被引用一次,計數器就加1,當對象引用時效一次就減,當計數器為0,意味着對象是垃圾對象,可以被GC回收。
2、可達性算法(GC Root Tracing):從GC Root作為起點開始搜索,那么整個連通圖中對象都是活的,對於GC Root無法達到的對象便是垃圾對象,隨時可被GC回收。
采用引用計數算法的系統只需在每個實例對象創建之初,通過計數器來記錄所有的引用次數即可。而可達性算法,則需要再次GC時,遍歷整個GC根節點來判斷是否回收。
下面通過一段代碼來對比說明:
public class GcDemo { public static void main(String[] args) { //分為6個步驟 GcObject obj1 = new GcObject(); //Step 1 GcObject obj2 = new GcObject(); //Step 2 obj1.instance = obj2; //Step 3 obj2.instance = obj1; //Step 4 obj1 = null; //Step 5 obj2 = null; //Step 6 } } class GcObject{ public Object instance = null; }
1、引用計數算法
如果采用的是引用計數算法:
再回到前面代碼GcDemo的main方法共分為6個步驟:
- Step1:GcObject實例1的引用計數加1,實例1的引用計數=1;
- Step2:GcObject實例2的引用計數加1,實例2的引用計數=1;
- Step3:GcObject實例2的引用計數再加1,實例2的引用計數=2;
- Step4:GcObject實例1的引用計數再加1,實例1的引用計數=2;
執行到Step 4,則GcObject實例1和實例2的引用計數都等於2。
接下來繼續結果圖:
- Step5:棧幀中obj1不再指向Java堆,GcObject實例1的引用計數減1,結果為1;
- Step6:棧幀中obj2不再指向Java堆,GcObject實例2的引用計數減1,結果為1。
到此,發現GcObject實例1和實例2的計數引用都不為0,那么如果采用的引用計數算法的話,那么這兩個實例所占的內存將得不到釋放,這便產生了內存泄露。
2、可達性算法
這是目前主流的虛擬機都是采用GC Roots Tracing算法,比如Sun的Hotspot虛擬機便是采用該算法。 該算法的核心算法是從GC Roots對象作為起始點,利用數學中圖論知識,圖中可達對象便是存活對象,
而不可達對象則是需要回收的垃圾內存。這里涉及兩個概念,一是GC Roots,一是可達性。
那么可以作為GC Roots的對象(見下圖):
- 虛擬機棧的棧幀的局部變量表所引用的對象;
- 本地方法棧的JNI所引用的對象;
- 方法區的靜態變量和常量所引用的對象;
關於可達性的對象,便是能與GC Roots構成連通圖的對象,如下圖:
從上圖,reference1、reference2、reference3都是GC Roots,可以看出:
- reference1-> 對象實例1;
- reference2-> 對象實例2;
- reference3-> 對象實例4;
- reference3-> 對象實例4 -> 對象實例6;
可以得出對象實例1、2、4、6都具有GC Roots可達性,也就是存活對象,不能被GC回收的對象。
而對於對象實例3、5直接雖然連通,但並沒有任何一個GC Roots與之相連,這便是GC Roots不可達的對象,這就是GC需要回收的垃圾對象。
到這里,相信大家應該能徹底明白引用計數算法和可達性算法的區別吧。
再回過頭來看看最前面的實例,GcObject實例1和實例2雖然從引用計數雖然都不為0,但從可達性算法來看,都是GC Roots不可達的對象。
總之,對於對象之間循環引用的情況,引用計數算法,則GC無法回收這兩個對象,而可達性算法則可以正確回收。