《理解Java虛擬機》第三章讀書筆記:垃圾收集器與內存分配

1. 概述

GC需要完成的事情
1、哪些內存需要回收
2、什麼時候回收
3、如何回收

2. 對象已死

2.1 引用計數算法

給對象中添加一個引用計算器,每當有一個地方引用它時,計數器加一,當引用失效時,計數器減一
。任何時刻都爲0的對象就是不可能再被使用了。

2.2 根搜索算法

通過一系列的名爲“GC Root”的對象起始點,從這些節點開始向下搜索,搜索所有走過的路徑稱爲引用鏈。當一個對象到“GC Root”沒有任何引用鏈相連。則證明對象不可用。

GC root對象包括如下
虛擬棧(棧中的引用變量表)中的引用的對象
方法區中的類靜態屬性引用的對象
方法區的常量引用的對象
本地方法棧中的JNI(native)方法

2.3 再談引用

強引用:只要強引用存在,垃圾回收器永遠不會回收掉被原因的對象
軟引用:在系統發將要發生內存溢出溢出之前,將會把這些對象列進回收範圍之中進行第二次回收
弱引用:被關聯的對象只能生存到下一次垃圾收集發生之前
虛引用:一個對象是否 有虛引用的存在,完全不會對其生存時間構成影響,也無法通過虛yiny取得一個對象實例

2.4 存在還是死亡

對象的死亡至少經過兩次標記
第一次:對象從根搜索發現沒有與GC Roots相連接的引用鏈,那它會被第一次標記並且進行一次篩選,篩選條件爲對象是否有必要執行finalize()。
第二次:對象執行了finalize()方法,那麼這個對象會被放置一個名爲F-Queue的隊列之中。
代碼清單:

package com.one.jvm;

/**
 * 測試對象只調用一次Finalize()方法
 * @author Administrator
 *
 */
public class FinalizeEscapeGC {


    public static FinalizeEscapeGC escapeGC = null;


    @Override
    protected void finalize() throws Throwable {
        // TODO Auto-generated method stub
        super.finalize();
        System.out.println("finalize method excuted");
        escapeGC = this;
    }

    public static void main(String[] args) throws InterruptedException {
        escapeGC = new FinalizeEscapeGC();

        escapeGC = null;
        //運行垃圾回收器。
        System.gc();
        Thread.sleep(600);

        if(escapeGC !=null) {
            System.out.println("escapeGC Object not  null");
        } else {
            System.out.println("escapeGC Object is null");
        }


        escapeGC = null;
        //運行垃圾回收器。
        System.gc();
        Thread.sleep(600);

        if(escapeGC !=null) {
            System.out.println("escapeGC Object not  null");
        } else {
            System.out.println("escapeGC Object is null");
        }
    }
}

2.5 方法區回收

類需要同時滿足下面三個條件才能算無用的類
1、該類所有的實例已經被回收,也就是Java堆中不存在該類的任何實例
2、加載該類的ClassLoder已經被回收
3、該類對應的java.lang.Class對象沒有在任何地方被引用,無 法通過任何地方通過反射訪問該類的方法

3. 垃圾收集算法

1、標記-清除算法(標記出所有需要回收的對象,在標記完成後統一回收所有被標記的對象)
缺點:1、標識與清除是效率不高,2、會產生大量不連續內存碎片
這裏寫圖片描述

2、複製算法:他將可用的內存按容量劃分大小相等的兩塊,每次只使用其中一塊。當這塊的內存用完了,就將存活的對象複製到另外一塊上面,然後再把已使用過得內存空間一次清理。(新生代比較適合)
這裏寫圖片描述

3、標記-整理算法:左右存活的對象都往一遍移,然後直接清理掉邊界以外的內存(老年代比較適合)
這裏寫圖片描述

4. 垃圾收集器

垃圾收集器是內存回收的具體實現

5. 內存分配與回收策略

5.1 對象優先在Eden分配

對象在新生代Eden區中分配內存,當Eden去沒有足夠的空間進行分配時,虛擬機將發起一次Minor GC

package com.one.jvm;

/**
 * -verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8
 * 參數解釋
 * -Xms20M:設置JVM促使內存爲10M。此值可以設置與-Xmx相同,以避免每次垃圾回收完成後JVM重新分配內存。
 * -Xmx20M :設置JVM最大可用內存爲20M
 * -Xmn10M :設置年輕代大小爲10M。整個堆大小=年輕代大小 + 年老代大小 + 持久代大小。持久代一般固定大小爲64m,所以增大年輕代後,將會減小年老代大小。此值對系統性能影響較大,Sun官方推薦配置爲整個堆的3/8。
 * -XX:PrintGCDetails,開啓詳細GC日誌模式
 * -XX:SurvivorRatio=8:年輕代中Eden區與兩個Survivor區的比值(8:1)
 * @author Administrator
 *
 */
public class EdenAllocation {

    private static final int _1MB = 1024*1024;

    public static void main(String[] args) {

        byte[] eden4 = new byte[2*_1MB];
        byte[] eden5 = new byte[2*_1MB];
        byte[] eden6 = new byte[2*_1MB];
        //新生代垃圾回收
        byte[] eden7 = new byte[2*_1MB];
    }
}

日誌

[GC [PSYoungGen: 6495K->304K(9216K)] 6495K->6448K(19456K), 0.0091599 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
[Full GC [PSYoungGen: 304K->0K(9216K)] [PSOldGen: 6144K->6328K(10240K)] 6448K->6328K(19456K) [PSPermGen: 2967K->2967K(21248K)], 0.0069367 secs] [Times: user=0.02 sys=0.00, real=0.01 secs] 
Heap
 PSYoungGen      total 9216K, used 2375K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
  eden space 8192K, 29% used [0x00000000ff600000,0x00000000ff851f98,0x00000000ffe00000)
  from space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000)
  to   space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000)
 PSOldGen        total 10240K, used 6328K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
  object space 10240K, 61% used [0x00000000fec00000,0x00000000ff22e140,0x00000000ff600000)
 PSPermGen       total 21248K, used 2984K [0x00000000f9a00000, 0x00000000faec0000, 0x00000000fec00000)
  object space 21248K, 14% used [0x00000000f9a00000,0x00000000f9cea3a0,0x00000000faec0000)

日誌參數含義

  • 第一行GC是指發生在新生代的垃圾收集動作,PSYoungGen表示Parallel Scavenge垃圾回收器,
    6495K->304K(9216K):6495K表示新生代垃圾回收前的大小,304K表示新生代垃圾回收後的大小。9216K表示新生代的大小。6495K->6448K(19456K):6495K表示整個堆回收前的大小,6448K表示整個堆回收後的大小,19456K表示整個堆內存分配大小

  • 第二行Full GC指發生在老年代的垃圾收集動作,PSYoungGen表示Parallel
    Scavenge垃圾回收器,304K->0K(9216K):304K表示新生代垃圾回收前的大小,0K表示新生代垃圾回收後的大小。 PSOldGen表示垃圾回收器,6144K->6328K(10240K):6144K表示老年代垃圾回收前的大小,6328K表示老年代垃圾回收後的大小。10240K表示整個老年代的大小。10240K表示整個老年代的大小。6448K->6328K(19456K):6448K表示整個堆回收前的大小,6328K表示整個堆回收的大小,19456K表示整個堆內存

GC內存走向分析

  • 第一次新生代GC的內存走向分析
    6495K->304K,新生代回收的內存大小(6191K=6495K-304K)。發出這樣的疑問?6191K到底去向何處。分析一下,
    6495K->6448K,整個堆回收的大小(47K)=6495K -
    6448K;剩下內存去那兒了(6191K-47K=6144K),哈哈,6144K去老年代了(有一段日誌:[PSOldGen:
    6144K->6328K(10240K)])
  • 第二次老年代GC的內存走向分析
    304K->0K,在進行老年代GC時,首先把新生代的內存回收完畢。那麼會產生一個疑問,清理的304K去向何處?分析一下,6144K->6328K,老年代增加了184K,整個內存堆回收了(6448K->6328K)120K。184K+120K=304K。

5.2 大對象直接進入老年代

所謂的大對象是指,需要大量連續內存空間的java對象,

package com.one.jvm;

/**
 * -verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8
 * 參數解釋
 * -Xms20M:設置JVM促使內存爲10M。此值可以設置與-Xmx相同,以避免每次垃圾回收完成後JVM重新分配內存。
 * -Xmx20M :設置JVM最大可用內存爲20M
 * -Xmn10M :設置年輕代大小爲10M。整個堆大小=年輕代大小 + 年老代大小 + 持久代大小。持久代一般固定大小爲64m,所以增大年輕代後,將會減小年老代大小。此值對系統性能影響較大,Sun官方推薦配置爲整個堆的3/8。
 * -XX:PrintGCDetails,開啓詳細GC日誌模式
 * -XX:SurvivorRatio=8:年輕代中Eden區與兩個Survivor區的比值(8:1)
 * @author Administrator
 *
 */
public class EdenAllocation {

    private static final int _1MB = 1024*1024;

    public static void main(String[] args) {

        byte[] eden1 = new byte[8*_1MB];

    }
}

GC日誌:
Heap
PSYoungGen total 9216K, used 515K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
eden space 8192K, 6% used [0x00000000ff600000,0x00000000ff680ee8,0x00000000ffe00000)
from space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000)
to space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000)
PSOldGen total 10240K, used 8192K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
object space 10240K, 80% used [0x00000000fec00000,0x00000000ff400010,0x00000000ff600000)
PSPermGen total 21248K, used 2975K [0x00000000f9a00000, 0x00000000faec0000, 0x00000000fec00000)
object space 21248K, 14% used [0x00000000f9a00000,0x00000000f9ce7e48,0x00000000faec0000)

長期存活的對象進入老年代

動態對象年齡判定

空間分配擔保

發佈了46 篇原創文章 · 獲贊 6 · 訪問量 16萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章