深入Java虛擬機-讀書筆記二 垃圾收集器

深入Java虛擬機-讀書筆記二

垃圾收集器

當我們討論垃圾收集的時候,也就是討論三個問題
1. 哪些內存需要回收?堆?方法區?方法區裏面的常量池?還是其他?
2. 什麼時候回收?內存不夠的時候回收還是定時回收?
3. 如何回收?回收的依據算法是什麼?

哪些內存需要回收?

前面筆記記錄Java內存運行時區域時,其中程序計數器,虛擬機棧,本地方法棧3個區域隨着線程而生命週期。因此這幾個區域的內存分配和回收都具備確定性,隨着方法結束或者線程結束時,內存自然就跟着回收了。
而Java堆和方法區則不一樣,一個接口中的多個實例類需要的內存可能不一樣,我們只有在程序運行期間才能知道會創建哪些對象,這部分內存的分配和回收都是動態的。垃圾收集器回收的就是這部分內存

堆和方法區需要垃圾收集器回收

如何判斷對象是否垃圾對象?

Java堆中存放着幾乎所有的對象實例,垃圾回收前,首先要做的就是確定哪些對象是垃圾對象

引用技術算法

給對象中添加一個引用計數器,每當有一個地方引用它時,計數器值就加1,當引用失效時,計數器值就減1,任何時候計數器爲0的對象就是不可能再被使用的。
基於該算法的實現簡單,判定效率也很高,在大部分情況下它還是一個不錯的算法。但是,目前主流的Java虛擬機裏面沒有選用改算法,主要原因是它很難解決對象之間相互循環引用的問題。比如對象A裏面有個對象B的引用,而B對象裏面也有個A對象的引用

可達性分析算法

可達性分析算法的思路就是通過一系列稱爲”GC Roots”的對象爲起始點,從這些節點開始往下搜索,搜索走過的路徑稱爲引用鏈,當一個對象到GC Roots沒有任何引用鏈連接時,則證明此對象是垃圾對象.

如下圖 objecet 5 object6 object 7雖然互相有關聯,但是沒有在GC Roots引用鏈中,所以它們都被認爲是可收回的垃圾對象
這裏寫圖片描述

那什麼對象又可被稱爲GC Roots對象呢?
Java中可作爲GC Roots如下幾種:
1. 虛擬機棧中應用的對象
2. 方法區中類靜態屬性引用的對象(static修飾的引用對象)
3. 方法區中常量引用的對象(final修飾引用對象)
4. 本地方法棧中JNI引用的對象(native方法應用的對象)

引用

Java中應用分爲4種,強引用,軟引用,弱引用,虛引用。

強引用 .

平常我們的代碼 類似”Object o=new Object()”,這類的引用,只要強引用還存在,垃圾收集器就永遠不會回收掉用引用的對象

軟引用

描述一些還有用但並非必需的對象。在系統將要發生內存溢出異常之前,將會把這些對象列進回收範圍之中進行第二次回收。SoftReference類來實現軟引用

弱引用

也是描述一些非必需的對象。每當垃圾回收器回收時,都會回收被弱引用關聯的對象。WeakReference類來實現弱引用

虛引用

稱爲幽靈引用或者幻影引用,它是最弱的一種引用關係。一個對象是否有虛引用的存在,完全不會對其生存時間構成影響。爲一個對象設置虛引用關聯的唯一目的就是能在這個對象被回收器回收是收到一個系統通知。PhantomReference類來實現弱引用

生存還是死亡,這是個問題。即使在可達性分析算法中不可達的而對象,也並非立即進行回收,這個時候它們暫時處於”緩刑”階段,要真正判斷一個對象已經死亡,至少要經歷兩次標記過程:如果對象在進行可達性分析後發現是垃圾對象,那它會被第一次標記並且進行一次篩選,篩選的條件是此對象是否有必要執行finalize()方法。當對象沒有覆蓋finalize()方法,或者finalize()方法已經被虛擬機調用過,這個時候纔會真正回收。

public class Test3 {
    static B b;
    static class B{
    public void println() {
            System.out.println("我還存活着....");
        }
        @Override
        protected void finalize() throws Throwable {
            super.finalize();
            System.out.println("finalize方法執行了....");
            //自我拯救
            Test3.b=this;
        }
    }
    public static void main(String[] args) throws Exception {
        b=new B();
        b=null;
        System.gc();
        Thread.sleep(500);
        if(b==null) {
            System.out.println("b已經被回收了....");
        }else {
            b.println();
        }
        b=null;
        System.gc();
        Thread.sleep(500);
        if(b==null) {
            System.out.println("b已經被回收了....");
        }else {
            b.println();
        }

    }
}

輸出日誌,可以看到finalize()方法是對象逃脫死亡的最後一個機會。並且finalize()方法都只會被系統自動調用一次。

finalize方法執行了….
我還存活着….
b已經被回收了….

垃圾收集算法

標記-清除算法

這是最基礎的算法:首先標記出所有需要回收的對象,在標記完成後統一回收所有被標記的對象

該算法主要有兩個不足:

一個是效率問題,標記和清除兩個過程的效率都不高
一個是空間問題,清除後會產生大量不連續的內存碎片
avatar

複製算法

爲了解決效率問題,”複製”算法出現,它將可用內存按容量劃分爲大小相等的兩塊,每次只使用其中的一塊。當這一塊的內存用完了,將還存活的對象複製到另外一塊上面,然後再把已經使用過的內存空間一次清理掉。

這種算法實現簡單,運行高效,但是代價是將內存縮小了原來的一半。
avatar

標記-整理算法

標記過程與標記-清除算法一樣,但後續不是直接對可回收對象進行清理,而是讓所有存活的對象都向一端移動,然後直接清理掉端邊界以外的內存。
avatar

分代收集算法

根據對象存活週期的不容將內存劃分爲幾塊。一般是把Java堆分爲新生代和老年代,這個可以根據各個年代的特點採用最適當的手機算法。在新生代,每次垃圾回收時都會發現有大批對象死去,只有少量存活,就選用複製算法。而老年代對象存活率高,則使用標記-整理或者標記-清理算法回收

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