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

内存分配与回收策略

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

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

堆中的分代如下图:


《深入理解Java虚拟机》(二)--垃圾收集器与内存分配策略(4)_第1张图片
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))