• 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触发条件:由新生代转入老年代时,对象内存占用大于平均转入老年代的对象内存占用。  

  • 相关阅读:
    实验3 简单的分支与循环结构 2.利用循环计算n个圆柱体体积。
    实验 3 简单的分支与循环结构 第一题 编写求圆面积的程序,要求当输入的半径r<=0时,提示输入错误,要求r为浮点型,r的数值是动态的由键盘输入
    心得3
    作业 3 应用分支与循环结构解决问题
    实验5 函数
    实验1 题目2
    第三,四章学习心得
    第2章学习心得
    第一章学习心得
    5-3
  • 原文地址:https://www.cnblogs.com/linzhanfly/p/9204795.html
Copyright © 2020-2023  润新知