天天看點

關于Java虛拟機的垃圾回收

JVM的記憶體區域配置設定

    要講垃圾收集算法,首先需要厘清楚JVM的記憶體區域配置設定:JVM的運作時資料區可以分為程式計數器(Program Counter Register)、虛拟機棧(VM Stack)、本地方法棧(Native Method Stack)、堆(Heap)、方法區(Method Area)五個部分。其中Sun HotSpot虛拟機将虛拟機棧和本地方法棧合二為一,并且方法區是堆的一個邏輯部分。而我們将要講到的垃圾回收就是發生在堆中。

    從垃圾收集的角度講,HotSpot虛拟機将堆區分為GC堆和方法區,也有人将其分為永生代(方法區)、新生代、老年代,其中新生代和老年代屬于GC堆,而新生代又可分為Eden空間和兩個 Survivior空間。至此,堆區的記憶體分布大緻已經講清楚了,下面我們将介紹幾種垃圾收集算法。

各垃圾收集算法的含義

标記-清除算法

    “标記-清除”算法是最基礎的收集算法,它分為“标記”和“清除”兩個階段:首先标記出所有需要回收的對象,标記完成後,統一回收所有标記的對象。之是以說它是最基礎的收集算法,是因為後續的收集算法都是基于這種思路并對其不足進而改進得到的。

    該方法主要有兩個不足:

    1.效率問題,标記和清除兩個過程的效率都不高

    2.空間問題,該方法清除之後會産生大量零散的記憶體空間,若之後程式遇到需要配置設定較大對象而記憶體空間不足時,會提前觸發下一次的垃圾收集動作。

标記-整理算法

    “标記-整理”算法過程與“标記-清理”算法一樣,隻是後續步驟不是直接對可回收對象進行清理,而是讓所有存活的對象都向一端移動,然後直接清理掉存活端邊界以外的記憶體。

複制算法

    “複制”算法将記憶體分為一塊較大的Eden空間和兩塊較小的Survivior空間,HotSpot虛拟機預設将三者的比例分為8:1:1,每次新生代的可用記憶體空間為整個新生代容量的90%(80%+10%)。當産生垃圾收集動作時,虛拟機會将存活的對象複制到剩下的Survivior空間,然後把已使用過的記憶體空間一次清理掉,這樣就不存在垃圾收集後空閑記憶體空間零散的情況;若回收後Survivior空間沒有足夠的空間存放上一次新生代收集下來的存活對象,就需要依賴其他記憶體(這裡指老年代)進行配置設定擔保。

對象已死?

    在垃圾收集器對堆進行回收之前,第一件事情就是要确定這些對象之中哪些還“存活”,哪些已經“死去”(即不可能再被任何途徑使用)。那麼我們怎麼判斷對象是否“死去”了呢?

引用計數算法

    該方法的實作原理是:給對象中添加一個引用計數器,每當一個地方引用它,計數器值加1;當引用失效時,計數器值減1;任何時刻計數器為0的對象就是不可能再被使用的。

    然而,這種方法有一個問題:它很難解決對象之間互相循環引用的狀況。舉個例子:這裡有對象A和對象B,有指派令A.instance = B 及 B.instance = A,除此之外,再無任何引用。實際上這兩個對象都不可能再被通路,但是因為它們互相引用着對方,使得引用計數器不為0,于是無法通知GC收集器回收它們。

可達性分析算法

    在主流的商用程式語言的主流實作中,都是稱通過可達性分析(Reachability Analysis)來判斷對象是否是存活的。

    這個算法的基本思路就是通過一系列的稱為“GC Roots”的對象作為起始點,從這些節點開始向下搜尋,搜尋所走過的路徑稱為引用鍊(Reference Chain),當一個對象沒有任何引用鍊相連時,則證明此對象是不可用的。

    在Java語言中,可作為GC Roots的對象包括以下4種:

    1.虛拟機棧中引用的對象。

    2.方法區中類靜态屬性引用的對象。

    3.方法區中常量引用的對象。

    4.本地方法棧中JNI(即一般的Native方法)引用的對象。

引用問題

    在 JDK1.2 之後,Java将引用分為了強引用、軟引用、弱引用和**虛引用**4種,這4種引用強度依次逐漸減弱。下面簡單介紹下這4種引用。

強引用

    強引用是最普遍的引用,類似于“Object a = new Object()”這樣。如果一個對象具有強引用,那麼垃圾收集器甯願抛出 OutOfMemoryError 錯誤也絕對不會回收它。

軟引用

    軟引用是一些還有用但并非必需的對象。軟引用會在記憶體空間不足時進行回收,軟引用可以用來實作記憶體敏感的高速緩存。如果軟引用被回收之後還是沒有足夠的記憶體,就會抛出記憶體溢出異常。

弱引用

    弱引用跟軟引用差不多,但是它的生命周期更短,在弱引用被垃圾收集器發現之後,不管記憶體夠不夠,都會被下一次的垃圾收集回收。

虛引用

    虛引用是最弱的一種引用關系,如果一個對象僅僅有一個虛引用,那麼它和沒有引用一樣,随時都可能被垃圾收集器回收。設定虛引用的唯一目的就是能在這個對象被收集器回收時收到一個系統通知。

各區垃圾收集方式

    幾條最普遍的記憶體配置設定規則,虛拟機根據對象存活周期的大小會将對象配置設定到相應的“年代區”:對象會優先在Eden區配置設定,大對象直接進入老年代,長期存活的對象将進入老年代(預設值為15,即垃圾收集15次之後依然存活),另外,當Survivior空間不夠時,存活對象會通過配置設定擔保機制進入老年代,class、常量等資訊直接加載進永生代。

    目前商業虛拟機的垃圾回收都采用“分代收集(Generational Collection)”算法,這種算法會根據各個年代的特點采用最适當的垃圾收集算法。在新生代中,每次垃圾收集時都發現有大批對象死去,隻有少量存活,是以選用複制算法,隻需要付出少量存活對象的複制成本就可以完成收集;而老年代因為對象存活率高、沒有額外空間對它進行配置設定擔保,就必須使用“标記-清理”或者“标記-整理”算法進行回收。