jvm GC:垃圾回收的測試與分析


實驗環境:

(1)Java版本以及模式:

  java version "1.8.0_171"
  Java(TM) SE Runtime Environment (build 1.8.0_171-b11)
  Java HotSpot(TM) 64-Bit Server VM (build 25.171-b11, mixed mode)

(2)垃圾回收器為Parrallel Scanvenge收集器 和 Parrallel Old收集器

(3)CPU:Intel(R) Core(TM) i5-7200U CPU @ 2.50GHz

 

1、查看基礎內存消耗

public class GCTest {
    public static void main(String[] args){}
}

//
使用javac編譯並執行java -verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 GCTest
// 日志如下:
Heap PSYoungGen total 9216K, used 1312K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000) eden space 8192K, 16% used [0x00000000ff600000,0x00000000ff748228,0x00000000ffe00000) from space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000) to space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000) ParOldGen total 10240K, used 0K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000) object space 10240K, 0% used [0x00000000fec00000,0x00000000fec00000,0x00000000ff600000) Metaspace used 2538K, capacity 4486K, committed 4864K, reserved 1056768K class space used 278K, capacity 386K, committed 512K, reserved 1048576K

分析日志可知:eden區基礎消耗1312K的內存,其中新生代大小為9216K = 8192 + 1024 K,因為 to 區和 from 區只有其中一個能使用。我並不知道基礎消耗到哪里去了?有知道的大神告訴我下。

注意:使用cmd命令行執行,基礎內存消耗較低,原先使用idea執行基礎內存消耗達到2M。 

2、新創建的對象內存大於新生代剩余內存將直接分配到老年代

import sun.misc.Unsafe;

import java.lang.reflect.Field;

public class GCTest {
    private static Unsafe unsafe;

    static {
        try {
            Field field = Unsafe.class.getDeclaredField("theUnsafe");
            field.setAccessible(true);
            unsafe = (Unsafe) field.get(null);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static long addressOf(Object o) throws Exception {
        Object[] array = new Object[] { o };
        long baseOffset = unsafe.arrayBaseOffset(Object[].class);
        int addressSize = unsafe.addressSize();
        long objectAddress;
        switch (addressSize) {
        case 4:
            objectAddress = unsafe.getInt(array, baseOffset);
            break;
        case 8:
            objectAddress = unsafe.getLong(array, baseOffset);
            break;
        default:
            throw new Error("unsupported address size: " + addressSize);
        }
        return (objectAddress);
    }

    private static final int _1MB = 1024 * 1024;

    public static void main(String[] args) throws Exception {
        byte[] allocation1, allocation2, allocation3, allocation4;
        allocation1 = new byte[2 * _1MB];
        allocation2 = new byte[2 * _1MB];
        allocation3 = new byte[2 * _1MB];
        allocation4 = new byte[4 * _1MB];
     // 打印對象所在的起始內存地址 System.out.println(
"allocation1:0x00000000" + Long.toHexString(addressOf(allocation1))); System.out.println("allocation2:0x00000000" + Long.toHexString(addressOf(allocation2))); System.out.println("allocation3:0x00000000" + Long.toHexString(addressOf(allocation3))); System.out.println("allocation4:0x00000000" + Long.toHexString(addressOf(allocation4))); } }

//
使用javac編譯並執行java -verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 GCTest
 
         
// 日志如下:
allocation1:0x00000000ff71f158 // 處於新生代
allocation2:0x00000000ff91f168 // 處於新生代 allocation3:0x00000000ffb1f178 // 處於新生代 allocation4:0x00000000fec00000 // 處於老年代 Heap PSYoungGen total 9216K, used 7456K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000) eden space 8192K, 91% used [0x00000000ff600000,0x00000000ffd48258,0x00000000ffe00000) from space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000) to space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000) ParOldGen total 10240K, used 4096K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000) object space 10240K, 40% used [0x00000000fec00000,0x00000000ff000010,0x00000000ff600000) Metaspace used 2568K, capacity 4486K, committed 4864K, reserved 1056768K class space used 284K, capacity 386K, committed 512K, reserved 1048576K

分析日志可知:Minor GC沒有觸發,由於新生代剩余大小也放不下allocation4,於是allocation4直接分配到老年代中。

 

3、Minor GC的觸發與過程

import sun.misc.Unsafe;

import java.lang.reflect.Field;

public class GCTest {
    private static Unsafe unsafe;

    static {
        try {
            Field field = Unsafe.class.getDeclaredField("theUnsafe");
            field.setAccessible(true);
            unsafe = (Unsafe) field.get(null);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static long addressOf(Object o) throws Exception {
        Object[] array = new Object[] { o };
        long baseOffset = unsafe.arrayBaseOffset(Object[].class);
        int addressSize = unsafe.addressSize();
        long objectAddress;
        switch (addressSize) {
        case 4:
            objectAddress = unsafe.getInt(array, baseOffset);
            break;
        case 8:
            objectAddress = unsafe.getLong(array, baseOffset);
            break;
        default:
            throw new Error("unsupported address size: " + addressSize);
        }
        return (objectAddress);
    }

    private static final int _1MB = 1024 * 1024;

    public static void main(String[] args) throws Exception {
        byte[] allocation1, allocation2, allocation3, allocation4;
        allocation1 = new byte[2 * _1MB];
        allocation2 = new byte[2 * _1MB];
        allocation3 = new byte[3 * _1MB]; // 觸發Minor GC
        allocation4 = new byte[2 * _1MB];
        System.out.println("allocation1:0x00000000" + Long.toHexString(addressOf(allocation1)));
        System.out.println("allocation2:0x00000000" + Long.toHexString(addressOf(allocation2)));
        System.out.println("allocation3:0x00000000" + Long.toHexString(addressOf(allocation3)));
        System.out.println("allocation4:0x00000000" + Long.toHexString(addressOf(allocation4)));
    }

}
// 使用javac編譯並執行java -verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 GCTest
// 日志如下:
 
         
[GC (Allocation Failure) [PSYoungGen: 5244K->768K(9216K)] 5244K->4872K(19456K), 0.0031067 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
allocation1:0x00000000fec00000 // 處於老年代 allocation2:0x00000000fee02010 // 處於老年代 allocation3:0x00000000ff600000 // 處於新生代 allocation4:0x00000000ff900010 // 處於新生代 Heap PSYoungGen total 9216K, used 6129K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000) eden space 8192K, 65% used [0x00000000ff600000,0x00000000ffb3c410,0x00000000ffe00000) from space 1024K, 75% used [0x00000000ffe00000,0x00000000ffec0030,0x00000000fff00000) to space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000) ParOldGen total 10240K, used 4104K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000) object space 10240K, 40% used [0x00000000fec00000,0x00000000ff002020,0x00000000ff600000) Metaspace used 2569K, capacity 4486K, committed 4864K, reserved 1056768K class space used 284K, capacity 386K, committed 512K, reserved 1048576K

分析日志:

(1)進行了一次Minor GC,其中新生代占用大小從5244K 減少為 768K,總占用從5244K 減少為 4872K,清理掉的內存大小 5244 - 4872 = 372K,則老年代大小為 5244 - 768 - 372 = 4104K,該值與最終的老年代大小占用相同。新生代的768K為from space持有,因為1024 * 75% = 768K.

(2) Minor GC的觸發條件:當Eden區域放不下新創建的對象,但是新生代的剩余空間大於新創建的對象大小時

  放入allocation1和allocation2時: 1314K(基礎消耗)+ 2 * 1024K(allocation1)+ 2*1024K(allocation2)= 5410K  小於Eden區

  放入allocation3時: 5410K + 3072K = 8482K 大於Eden區域(8192K) 小於新生代大小(9216K) 此時觸發Minor GC

 

Minor GC的過程:

(1)將存活對象從Eden區將對象轉入from區域(如:上述from space的內存占用為768K)。

(2)轉移期間,對象太大,將直接進入老年代(如:上述老年代的4104K的內存大小)。

(3)轉移后,非存活對象將被清理掉(如:上述372K的內存大小)。

 

4、Full GC觸發與過程

import sun.misc.Unsafe;

import java.lang.reflect.Field;

public class GCTest {
    private static Unsafe unsafe;

    static {
        try {
            Field field = Unsafe.class.getDeclaredField("theUnsafe");
            field.setAccessible(true);
            unsafe = (Unsafe) field.get(null);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static long addressOf(Object o) throws Exception {
        Object[] array = new Object[] { o };
        long baseOffset = unsafe.arrayBaseOffset(Object[].class);
        int addressSize = unsafe.addressSize();
        long objectAddress;
        switch (addressSize) {
        case 4:
            objectAddress = unsafe.getInt(array, baseOffset);
            break;
        case 8:
            objectAddress = unsafe.getLong(array, baseOffset);
            break;
        default:
            throw new Error("unsupported address size: " + addressSize);
        }
        return (objectAddress);
    }

    private static final int _1MB = 1024 * 1024;

    public static void main(String[] args) throws Exception {
        byte[] allocation1, allocation2, allocation3, allocation4;
        allocation1 = new byte[2 * _1MB];
        allocation2 = new byte[3 * _1MB]; // 轉入老年代時,由於allocation2 大於 allocation1,所以觸發了Full GC
        allocation3 = new byte[2 * _1MB]; // 觸發Minor GC
        System.out.println("allocation1:0x00000000" + Long.toHexString(addressOf(allocation1)));
        System.out.println("allocation2:0x00000000" + Long.toHexString(addressOf(allocation2)));
        System.out.println("allocation3:0x00000000" + Long.toHexString(addressOf(allocation3)));
    }
}

// 使用javac編譯並執行java -verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 GCTest
日志如下:
[GC (Allocation Failure) [PSYoungGen: 6268K->768K(9216K)] 6268K->5896K(19456K), 0.0036739 secs] [Times: user=0.01 sys=0.05, real=0.02 secs] [Full GC (Ergonomics) [PSYoungGen: 768K->0K(9216K)] [ParOldGen: 5128K->5750K(10240K)] 5896K->5750K(19456K), [Metaspace: 2559K->2559K(1056768K)], 0.0068489 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] allocation1:0x00000000fec00000 // 處於老年代 allocation2:0x00000000fee004e8 // 處於老年代 allocation3:0x00000000ff600000 // 處於新生代 Heap PSYoungGen total 9216K, used 2289K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000) eden space 8192K, 27% used [0x00000000ff600000,0x00000000ff83c770,0x00000000ffe00000) from space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000) to space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000) ParOldGen total 10240K, used 5750K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000) object space 10240K, 56% used [0x00000000fec00000,0x00000000ff19d8e8,0x00000000ff600000) Metaspace used 2569K, capacity 4486K, committed 4864K, reserved 1056768K class space used 284K, capacity 386K, committed 512K, reserved 1048576K  
 
        

分析日志:
(1)發生一次Minor GC與一次Full GC,第一次Minor GC回收了 6268K - 5896K = 372K 內存,新生代剩余768K內存,這一步和上一個例子相同。
(2)由於allocation1和allocation2的大小大於from space區,所以需要直接轉入老年代。但由於allocation2大於allocation1,觸發Full GC來防止老年代的空間不足以存放新的對象。
這次GC回收了5896K - 5750K = 146K的內存空間,新生代內存占用變為0K,老年代內存占用添加768K - 146K = 622K。老年代內存占用= 622K+allocation1(2*1024)+allocation2(3*1024) = 5742K (約等於5750K)
多出來的8K,我也不知道在哪里了,有大神知道請告訴我。
(3)allocation3直接放入了Eden區域,新生代最終內存占用為 2048K(約等於2289K)。



Full GC的過程:
(1)一般會觸發一次Minor GC,即from space區對象的存活時間較長能夠升入老年代。
(2)老年代垃圾回收,將非存活對象回收。

5、總結

(1)Minor GC觸發條件:新創建的對象大於Eden區剩余空間,小於新生代剩余空間時觸發;Full GC時伴隨觸發一次。

(2)Full GC觸發條件:由新生代轉入老年代時,對象內存占用大於平均轉入老年代的對象內存占用。  

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM