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

  • 相关阅读:
    Java实现 LeetCode 50 Pow(x,n)
    Java实现 LeetCode 50 Pow(x,n)
    Java实现 LeetCode 49 字母异位词分组
    Java实现 LeetCode 49 字母异位词分组
    Java实现 LeetCode 49 字母异位词分组
    Java实现 LeetCode 48 旋转图像
    Java实现 LeetCode 48 旋转图像
    Java实现 LeetCode 48 旋转图像
    Java实现 LeetCode 47 全排列 II(二)
    Java实现 LeetCode 47 全排列 II(二)
  • 原文地址:https://www.cnblogs.com/linzhanfly/p/9204795.html
Copyright © 2011-2022 走看看