首頁  >  文章  >  Java  >  Java GC 的那些事(1)

Java GC 的那些事(1)

黄舟
黄舟原創
2017-02-22 10:08:371408瀏覽

前言

與C語言不同,Java記憶體(堆記憶體)的分配與回收由JVM垃圾收集器自動完成,這個特性深受大家歡迎,能夠幫助程式設計師更好的編寫程式碼,本文以HotSpot虛擬機為例,說一說Java GC的那些事。

Java堆記憶體

在JVM記憶體的那些事一文中,我們已經知道Java堆是被所有執行緒共享的一塊記憶體區域,所有物件實例和陣列都在堆上進行記憶體分配。為了進行高效率的垃圾回收,虛擬機器把堆記憶體劃分成新生代(Young Generation)、老年代(Old Generation)和永久代(Permanent Generation)3個區域。

Java GC 的那些事(1)

新生代

新生代由Eden 與Survivor Space(S0,S1)構成,大小透過-Xmn參數指定, Eden 與Survivor Space 的記憶體大小比例預設為8:1,可以透過-XX:SurvivorRatio 參數指定,例如新生代為10M 時,Eden分配8M,S0和S1各分配1M。

Eden:希臘語,意思是伊甸園,在聖經中,伊甸園含有樂園的意思,根據《舊約·創世紀》記載,上帝耶和華照自己的形象造了第一個男人亞當,再用亞當的一個肋骨創造了一個女人夏娃,並安置他們住在了伊甸園。

大多數情況下,物件在Eden中分配,當Eden沒有足夠空間時,會觸發一次Minor GC,虛擬機器提供了-XX:+PrintGCDetails參數,告訴虛擬機器在發生垃圾回收時列印內存回收日誌。

Survivor:意思是倖存者,是新生代和老年代的緩衝區域。

當新生代發生GC(Minor GC)時,會將存活的物件移到S0記憶體區域,並清空Eden區域,當再次發生Minor GC時,將Eden和S0中存活的物件移到S1記憶體區。

存活物件會反覆在S0和S1之間移動,當物件從Eden移動到Survivor或Survivor之間移動時,物件的GC年齡會自動累加,當GC年齡超過預設閾值15時,會將該物件移到老年代,可以透過參數-XX:MaxTenuringThreshold 對GC年齡的閾值進行設定。

老年代

老年代的空間大小即-Xmx 與-Xmn 兩個參數之差,用於存放經過幾次Minor GC之後依舊存活的對象。當老年代的空間不足時,會觸發Major GC/Full GC,速度一般比Minor GC慢10倍以上。

永久代

在JDK8之前的HotSpot實作中,類別的元資料如方法資料、方法資訊(字節碼,堆疊和變數大小)、執行時常數池、已確定的符號引用和虛方法表等被保存在永久代中,32位元預設永久代的大小為64M,64位元預設為85M,可以透過參數-XX:MaxPermSize進行設置,一旦類別的元資料超過了永久代大小,就會拋出OOM異常。

虛擬機團隊在JDK8的HotSpot中,把永久代從Java堆中移除了,並把類別的元資料直接保存在本地記憶體區域(堆外記憶體),稱之為元空間。

這樣做有什麼好處?

有經驗的同學會發現,對永久代的調優過程非常困難,永久代的大小很難確定,其中涉及到太多因素,如類的總數、常量池大小和方法數量等,而且永久代的資料可能會隨著每一次Full GC而發生移動。

而在JDK8中,類別的元資料保存在本地記憶體中,元空間的最大可分配空間就是系統可用記憶體空間,可以避免永久代的記憶體溢位問題,不過需要監控記憶體的消耗情況,一旦發生記憶體洩漏,會佔用大量的本地記憶體。

ps:JDK7之前的HotSpot,字串常數池的字串被儲存在永久代中,因此可能導致一系列的效能問題和記憶體溢出錯誤。在JDK8中,字串常數池中只保存字串的參考。

如何判斷物件是否存活

GC動作發生之前,需要確定堆記憶體中哪些物件是存活的,一般有兩種方法:引用計數法和可達性分析法。

1、引用計數法

在物件上新增一個引用計數器,每當有一個物件引用它時,計數器加1,當使用完該物件時,計數器減1,計數器值為0的物件表示不可能再被使用。

引用計數法實現簡單,判定高效,但不能解決物件之間相互引用的問題。

public class GCtest {
    private Object instance = null;
    private static final int _10M = 10 * 1 << 20;
    // 一个对象占10M,方便在GC日志中看出是否被回收
    private byte[] bigSize = new byte[_10M];
    public static void main(String[] args) {
        GCtest objA = new GCtest();
        GCtest objB = new GCtest();
        objA.instance = objB;
        objB.instance = objA;
        objA = null;
        objB = null;
        System.gc();
    }
}

透過新增-XX:+PrintGC參數,執行結果:

[GC (System.gc()) [PSYoungGen: 26982K->1194K(75776K)] 26982K->1202K(249344K), 0.0010103 secs]


从GC日志中可以看出objA和objB虽然相互引用,但是它们所占的内存还是被垃圾收集器回收了。

2、可达性分析法

通过一系列称为 “GC Roots” 的对象作为起点,从这些节点开始向下搜索,搜索路径称为 “引用链”,以下对象可作为GC Roots:

本地变量表中引用的对象

方法区中静态变量引用的对象

方法区中常量引用的对象

Native方法引用的对象

当一个对象到 GC Roots 没有任何引用链时,意味着该对象可以被回收。

Java GC 的那些事(1)

在可达性分析法中,判定一个对象objA是否可回收,至少要经历两次标记过程:

1、如果对象objA到 GC Roots没有引用链,则进行第一次标记。

2、如果对象objA重写了finalize()方法,且还未执行过,那么objA会被插入到F-Queue队列中,由一个虚拟机自动创建的、低优先级的Finalizer线程触发其finalize()方法。finalize()方法是对象逃脱死亡的最后机会,GC会对队列中的对象进行第二次标记,如果objA在finalize()方法中与引用链上的任何一个对象建立联系,那么在第二次标记时,objA会被移出“即将回收”集合。

看看具体实现

public class FinalizerTest {
    public static FinalizerTest object;
    public void isAlive() {
        System.out.println("I&#39;m alive");
    }
    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println("method finalize is running");
        object = this;
    }
    public static void main(String[] args) throws Exception {
        object = new FinalizerTest();
        // 第一次执行,finalize方法会自救
        object = null;
        System.gc();
        Thread.sleep(500);
        if (object != null) {
            object.isAlive();
        } else {
            System.out.println("I&#39;m dead");
        }
        // 第二次执行,finalize方法已经执行过
        object = null;
        System.gc();
        Thread.sleep(500);
        if (object != null) {
            object.isAlive();
        } else {
            System.out.println("I&#39;m dead");
        }
    }
}

执行结果:

method finalize is running
I&#39;m alive
I&#39;m dead

从执行结果可以看出:

第一次发生GC时,finalize方法的确执行了,并且在被回收之前成功逃脱;

第二次发生GC时,由于finalize方法只会被JVM调用一次,object被回收。

当然了,在实际项目中应该尽量避免使用finalize方法。

Java GC 的那些事(1)

Java GC的那些事(2)

以上就是Java GC 的那些事(1)的内容,更多相关内容请关注PHP中文网(www.php.cn)!


陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn