五、JVM系列(垃圾回收機制)

前言

在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垃圾回收機制


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章