内存分配与回收策略
对象的内存分配规则不是固定的,是取决于你使用的是哪种垃圾回收器组合和虚拟机中的内存参数,如果启动了本地线程分配缓冲,将按线程优先在TLAB(之前提到过)上分配,接下来将验证几种常见的分配策略基于JDK1.8(书上是1.7和我测试的结果完全不一样QAQ,这应该是因为使用的垃圾回收器不一样所导致的)。
1/1 对象优先在Eden上分配
堆中的分代如下图:
大多数情况下,对象在新生代中的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%。
好吧,我放弃了,这个并行收集器的组合回收垃圾并不像串行收集器那么简单,测试的结果和我想的根本就对不上,只能说这个收集器更加的智能,它就是那种能不进行垃圾回收就不进行的......