前言
在Java中,如果一個對象不可能再被引用,那麼這個對象就是垃圾,應該被回收,以防暫用內存。
如何判斷垃圾?
1、引用計數法
我們可以很容易想到,通過引用計數的方法,當一個對象被引用的時候,引用+1,去除引用的時候-1,即引用數量爲0的時候該對象就是垃圾,應該被回收。
例如下面的例子:
例子1
String a = new String("你好");
a = null;
我們創建了一個String類型的對象,對象值是“你好“ 由引用a 指向它。此時我們可以理解成該對象被a引用,此時它的引用+1
。接着我們讓a = null。即去除該對象的引用。此時引用-1.最後引用數量爲0,因此該對象就被視爲垃圾,應該被回收。
例子2
public class ReferenceCountingGC {
public Object instance;
public ReferenceCountingGC(String name) {
}
public static void testGC(){
ReferenceCountingGC a = new ReferenceCountingGC("objA");
ReferenceCountingGC b = new ReferenceCountingGC("objB");
a.instance = b;
b.instance = a;
a = null;
b = null;
}
}
例子2中,我們分別創建了兩個ReferenceCountingGC 對象,由a和b 指向,並且兩個對象中有一個成員變量instance。
接着我們讓 a.instance = b;即對象a中的instance變量指向了b指向的引用, b.instance = a;對象b中的instance 指向了a指向的引用。然後分別a=null,b=null。我們看下圖:
雖然原先a和b對象 沒有被a,b引用了。但是a和b對象中的instance相互引用了對方。即a對象中的instance指向了b對象。b對象中的instance指向了a對象。即a和b對象都被引用着,即使我們設置了a = null,b=null。他們的引用數量也不爲0。所以通過引用計數法就沒法判斷他們是否是垃圾,從而沒法進行回收,浪費內存。
2、GC Root Tracing算法(可達性分析算法)。
GC Root Tracing算法大概的過程是:
從GC Root Tracing出發,所有可達的對象都是存活的對象,不可達的對象視爲垃圾。而什麼是GC Root Tracing呢?
GC Root Tracing就是一組活躍的引用集合,大概包括:
- 所有當前被加載的類
- Java類中的引用類型靜態變量
- Java類的運行時常量池裏的引用類型常量
- VM的一些靜態數據結構指向GC堆裏的對象引用
- 等
大概流程可以看如下圖:
如何進行垃圾回收?
當JVM識別出了垃圾之後,如何進行回收呢?垃圾回收簡單的說有三種算法:標記清除算法、複製算法、標記壓縮算法
- 標記清除算法
分爲標記和清除階段,在標記階段,標記所有從GC Root出發可達的對象,此時不可達的對象就是垃圾對象。之後在清除階段將沒有標記的對象清除。缺點就是:導致會產生很多內存碎片,即內存不連續。雖然對象可以分配在不連續的內存空間中,但是這樣效率要低於連續的內存空間。 - 複製算法
將原有的內存分爲兩塊,每次只使用一塊。在垃圾回收的時候,通過 GC Root Tracing算法,將正在使用的內存中活躍的對象複製到未使用的內存塊中。之後清除正在使用的內存塊中的對象。然後交換兩個內存塊的角色,完成垃圾回收。該算法的的缺點就是要將內存摺半,大大浪費內存 - 標記壓縮算法
該算法是標記清除算法的優化版本,分別經歷了標記、壓縮階段。標記階段我們都知道了,而在壓縮階段中,則將所有存活的對象壓縮在內存的一邊,之後將外界的對象回收。 - 小結
標記清除算法,雖然會產生很多的內存碎片,但是不需要移動太多對象,比較適合對象存活多的時候。
複製算法,雖然會將內存摺半,但是可以使得內存連續,適合對象少的情況。
標記壓縮算法則是標記清除算法的優化版本,也要經過標記,但是隻是壓縮到另一邊。使得保持內存連續。
分代思想
以上我們介紹了三種垃圾回收算法,但是在JVM實際進行垃圾回收的時候則不是單獨的只用某一種算法。而是根據實際情況,採用不同的算法。
所謂分代算法:就是根據JVM不同的內存區域,採用不同的垃圾回收算法。例如對於存活對象少的新生代區域,採用複製算法,因爲對象比較少,可以將需要回收和不需要回收的對象移動到未使用區域和使用區域。對於存活對象比較多的老年代區域,則可以採用標記清除或者標記壓縮算法,因爲這樣一來就不用移動太多對象。
分區思想
以上分代思想是根據對象的數量多少來劃分,新生代和老年代。分區思想則是根據對象的生命週期長短來劃分老年代和新生代。然後根據不同代採用不同的垃圾回收算法。但在JVM中其實還有一個分區思想,就是將整個堆空間劃分成連續的不同小區域,每個期間都獨立使用。獨立回收。以下圖我們來小結以上分析的JVM垃圾回收機制