美文网首页
《深入理解Java虚拟机》(二)--垃圾收集器与内存分配策略(4

《深入理解Java虚拟机》(二)--垃圾收集器与内存分配策略(4

作者: 蓝色_fea0 | 来源:发表于2018-06-01 12:14 被阅读62次

    内存分配与回收策略

    对象的内存分配规则不是固定的,是取决于你使用的是哪种垃圾回收器组合和虚拟机中的内存参数,如果启动了本地线程分配缓冲,将按线程优先在TLAB(之前提到过)上分配,接下来将验证几种常见的分配策略基于JDK1.8(书上是1.7和我测试的结果完全不一样QAQ,这应该是因为使用的垃圾回收器不一样所导致的)。

    1/1 对象优先在Eden上分配

    堆中的分代如下图:


    image.png

    大多数情况下,对象在新生代中的Eden上分配,当Eden没有足够的空间时,虚拟机会发动一次Minor GC。
    Java中的一些GC:

    • Minor GC(新生代GC):指的是发生在新生代的垃圾收集动作,因为Java对象大多创建和销毁非常的频繁,所以Minor GC也非常频繁,回收的速度也比较快。
    • Major GC(老年代GC):指发生在老年代的GC,一般出现了Major GC,经常会伴随至少一次的MinorGC,它的速度比较慢。
    • Full GC:指的是清理整个空间的GC,包括老年代和新生代。
      举个栗子:
    /**
         * 我将老年代和新生代的内存都设成了10M,Eden和Survivor的比为8:1:1
         * 虚拟机参数:
         * -verbose:gc -Xms20M -Xms20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8
         */
        private static final int _1MB = 1024*1024;
    
        public static void main(String[] args) {
            byte[] a1,a2,a3,a4;
            a1 = new byte[2*_1MB];
            a2 = new byte[3*_1MB];
        }
    //打印结果:
    Heap
     PSYoungGen (Parallel Scavenge)     total 9216K, used 7647K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
    (新生代)
      eden space 8192K, 93% used [0x00000000ff600000,0x00000000ffd77c28,0x00000000ffe00000)
      from space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000)
      to   space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000)
     ParOldGen       total 10240K, used 0K [0x0000000083200000, 0x0000000083c00000, 0x00000000ff600000)
    (老年代)
      object space 10240K, 0% used [0x0000000083200000,0x0000000083200000,0x0000000083c00000)
    (元空间也就是永久代)
     Metaspace       used 3464K, capacity 4496K, committed 4864K, reserved 1056768K
      class space    used 378K, capacity 388K, committed 512K, reserved 1048576K
    

    从上面的代码和打印结果可以看出JDK1.8默认的垃圾收集器组合是Parallel Scavenge和Parallel Old 而JDK1.7则默认使用的是Serial和Serial Old(书上写的)。我们发现Eden区域(新生代)占了百分之93,而老年代(object space 10240K, 0% used)没有使用。
    接下来我们再加入一个1M的对象试试:

    a1 = new byte[2*_1MB];
    a2 = new byte[3*_1MB];
    //加入一个1M的对象
    a3 = new byte[1*_1MB];
    //打印结果:
    [GC (Allocation Failure) [PSYoungGen: 7482K->840K(9216K)] 7482K->5968K(19456K), 0.0034355 secs] [Times: user=0.06 sys=0.00, real=0.00 secs] 
    [Full GC (Ergonomics) [PSYoungGen: 840K->0K(9216K)] [ParOldGen: 5128K->5805K(24576K)] 5968K->5805K(33792K), [Metaspace: 3445K->3445K(1056768K)], 0.0054131 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
    Heap
     PSYoungGen      total 9216K, used 1190K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
      (新生代)
    eden space 8192K, 14% used [0x00000000ff600000,0x00000000ff729810,0x00000000ffe00000)
      from space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000)
      to   space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000)
     ParOldGen       total 24576K, used 5805K [0x0000000083200000, 0x0000000084a00000, 0x00000000ff600000)
      (老年代)
    object space 24576K, 23% used [0x0000000083200000,0x00000000837ab400,0x0000000084a00000)
    (元空间也就是永久代)
     Metaspace       used 3461K, capacity 4496K, committed 4864K, reserved 1056768K
      class space    used 377K, capacity 388K, committed 512K, reserved 1048576K
    

    可以看出,我们加了一个1M的对象a3,然而新生代的空间并不足够放下它,虚拟机则进行了一次Full GC,将原本新生代的对象转移到老年代,并将老年代的空间扩充至20M,之后再将1M的对象放在新生代中。
    如果再加入一个6M的对象,因为新生代足够用,所以会直接放到新生代中,但是如果加一个8M的对象(新生代的Eden无法容纳这个对象),则会直接放入老年代(别问我为什么知道的,因为我测试过了,太懒了,代码很简单,就不往上沾了)。可以看出对象确实是优先分配在Eden上的。

    1/2 大对象直接进入老年代

    大对象就是指需要大量连续内存的Java对象,比如一个很长很长的字符串或者数组,虚拟机提供了一个-XX:PretenureSizeThreshold参数,可以令大于这个值的对象直接在老年代上分配。
    举个栗子:

    /**
         * 我将老年代和新生代的内存都设成了10M,Eden和Survivor的比为8:1:1
         * 虚拟机参数:
         * -verbose:gc -Xms20M -Xms20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8
         *  -XX:PretenureSizeThreshold=3145728
         */
        private static final int _1MB = 1024*1024;
    
        public static void main(String[] args) throws InterruptedException {
            byte[] a = new byte[6*_1MB];
        }
    //打印结果:
    Heap
     PSYoungGen      total 9216K, used 2527K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
      eden space 8192K, 30% used [0x00000000ff600000,0x00000000ff877c08,0x00000000ffe00000)
      from space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000)
      to   space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000)
     ParOldGen       total 10240K, used 6144K [0x0000000083200000, 0x0000000083c00000, 0x00000000ff600000)
      object space 10240K, 60% used [0x0000000083200000,0x0000000083800010,0x0000000083c00000)
     Metaspace       used 3464K, capacity 4496K, committed 4864K, reserved 1056768K
      class space    used 378K, capacity 388K, committed 512K, reserved 1048576K
    

    我们可以看出,这个6M的对象直接分配在老年代上了,老年代的空间被占用60%。
    好吧,我放弃了,这个并行收集器的组合回收垃圾并不像串行收集器那么简单,测试的结果和我想的根本就对不上,只能说这个收集器更加的智能,它就是那种能不进行垃圾回收就不进行的......

    相关文章

      网友评论

          本文标题:《深入理解Java虚拟机》(二)--垃圾收集器与内存分配策略(4

          本文链接:https://www.haomeiwen.com/subject/dnyxsftx.html