垃圾收集器与内存分配策略(五)
内存分配和回收策略(二)
4、动态对象年龄判定
为了更好的适应不同程序的内存状况,虚拟机并不是强制要求对象年龄必须达到MaxTenuringThreshold才能晋升老年代。如果在Survivor空间中相同年龄对象大小总和大于Survivor空间一半,年龄大于或等于该年龄的对象就可以直接进入老年代。比如有很多的age=n的对象,他们内存之和>Survivor空间一半,则>n的年龄对象直接进入老年代。
/**
* -XX:+UseSerialGC 使用SerialGC垃圾收集器
* -verbose:gc 输出虚拟机中GC的详细情况
* -Xms20M Java堆最大20M
* -Xmx20M Java堆最小20M(说明不可扩展)
* -Xmn10M 10M分给新生代
* -XX:+PrintGCDetails
* -XX:SurvivorRatio=8 新生代Eden区与一个Survivor区的空间比例是8:1
* -XX:MaxTenuringThreshold=15 当age=15时,放入老年代
* -XX:+PrintTenuringDistribution
* -XX:+HandlePromotionFailure
*
* @author wcj
* @date 2019/9/23 16:56
*/
public class AgeObjectGC {
private static final int _1MB = 1024 * 1024;
public static void main(String[] args) {
byte[] b1, b2, b3, b4;
b1 = new byte[_1MB / 4];//256k 新生代10M,Eden区与Survivor区的大小占比为8M,1M
b2 = new byte[_1MB / 4];//256k 老年代10M
b3 = new byte[4 * _1MB];//4MB
b4 = new byte[4 * _1MB];//4MB
b4 = null;
b4 = new byte[4 * _1MB];//4MB
}
}
输出结果如下:
[GC (Allocation Failure) [DefNew
Desired survivor size 524288 bytes, new threshold 1 (max 15)
- age 1: 944488 bytes, 944488 total
: 6288K->922K(9216K), 0.0042134 secs] 6288K->5018K(19456K), 0.0042469 secs] [Times: user=0.00 sys=0.01, real=0.00 secs]
[GC (Allocation Failure) [DefNew
Desired survivor size 524288 bytes, new threshold 15 (max 15)
- age 1: 280 bytes, 280 total
: 5100K->0K(9216K), 0.0009777 secs] 9196K->5001K(19456K), 0.0009950 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Heap
def new generation total 9216K, used 4315K [0x00000006c0000000, 0x00000006c0a00000, 0x00000006c0a00000)
eden space 8192K, 52% used [0x00000006c0000000, 0x00000006c0436ce0, 0x00000006c0800000)
from space 1024K, 0% used [0x00000006c0800000, 0x00000006c0800118, 0x00000006c0900000)
to space 1024K, 0% used [0x00000006c0900000, 0x00000006c0900000, 0x00000006c0a00000)
tenured generation total 10240K, used 5001K [0x00000006c0a00000, 0x00000006c1400000, 0x00000007c0000000)
the space 10240K, 48% used [0x00000006c0a00000, 0x00000006c0ee2580, 0x00000006c0ee2600, 0x00000006c1400000)
Metaspace used 3194K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 354K, capacity 388K, committed 512K, reserved 1048576K
由结果发现,当设置-XX:MaxTenuringThreshold=15时,新生代中的Survivor空间仍然是0%,因为新生代Eden区与一个Survivor区的空间比例是8:1,所以当b4分配时,剩余的Eden区不够分配4M的内存空间,所以执行到b4时,进行了Minor GC,将b1,b2,b3进行了回收,b3太大(4M)进入不了Survivor(1M),就直接回收进了老年代,而b1,b2进入了Survivor。如下图
byte[] b1, b2, b3, b4;
b1 = new byte[_1MB / 4];//256k 新生代10M,Eden区与Survivor区的大小占比为8M,1M
b2 = new byte[_1MB / 4];//256k 老年代10M
b3 = new byte[4 * _1MB];//4MB
b4 = new byte[4 * _1MB];//4MB
这是输出结果如下:
[GC (Allocation Failure) [DefNew
Desired survivor size 524288 bytes, new threshold 1 (max 15)
- age 1: 944488 bytes, 944488 total
: 6288K->922K(9216K), 0.0084268 secs] 6288K->5018K(19456K), 0.0084855 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]
Heap
def new generation total 9216K, used 5182K [0x00000006c0000000, 0x00000006c0a00000, 0x00000006c0a00000)
eden space 8192K, 52% used [0x00000006c0000000, 0x00000006c04290f0, 0x00000006c0800000)
from space 1024K, 90% used [0x00000006c0900000, 0x00000006c09e6968, 0x00000006c0a00000)
to space 1024K, 0% used [0x00000006c0800000, 0x00000006c0800000, 0x00000006c0900000)
tenured generation total 10240K, used 4096K [0x00000006c0a00000, 0x00000006c1400000, 0x00000007c0000000)
the space 10240K, 40% used [0x00000006c0a00000, 0x00000006c0e00010, 0x00000006c0e00200, 0x00000006c1400000)
Metaspace used 3181K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 350K, capacity 388K, committed 512K, reserved 1048576K
这是,可以看出老年代中,使用了4096K,也就是b3进入了老年代,b4进入了Eden区。当执行到最后一行b4=4M时,根据分配原则,优先在Eden分配,但是Eden剩余的空间不足以分配4M的b4,发送了一个Minor GC,因为Survivor区的空间的b1,b2同年龄且占用了Survivor空间的一半,所以直接分配到了老年代,老年代的占用率达到了48%。如果将b4=3M,则不会触发第二次Minor GC,b1,b2还是在新生代的Survivor中。如下图
byte[] b1, b2, b3, b4,b5;
b1 = new byte[_1MB / 4];//256k 新生代10M,Eden区与Survivor区的大小占比为8M,1M
b2 = new byte[_1MB / 4];//256k 老年代10M
b3 = new byte[4 * _1MB];//4MB
b4 = new byte[4 * _1MB];//4MB
b4 = null;
b4 = new byte[3 * _1MB];//3MB
将b4赋值为3M时,应该Eden区,占用4+3M,结果如下:
[GC (Allocation Failure) [DefNew
Desired survivor size 524288 bytes, new threshold 1 (max 15)
- age 1: 944584 bytes, 944584 total
: 6288K->922K(9216K), 0.0039214 secs] 6288K->5018K(19456K), 0.0039455 secs] [Times: user=0.01 sys=0.01, real=0.01 secs]
Heap
def new generation total 9216K, used 8414K [0x00000006c0000000, 0x00000006c0a00000, 0x00000006c0a00000)
eden space 8192K, 91% used [0x00000006c0000000, 0x00000006c0750ef0, 0x00000006c0800000)
from space 1024K, 90% used [0x00000006c0900000, 0x00000006c09e69c8, 0x00000006c0a00000)
to space 1024K, 0% used [0x00000006c0800000, 0x00000006c0800000, 0x00000006c0900000)
tenured generation total 10240K, used 4096K [0x00000006c0a00000, 0x00000006c1400000, 0x00000007c0000000)
the space 10240K, 40% used [0x00000006c0a00000, 0x00000006c0e00010, 0x00000006c0e00200, 0x00000006c1400000)
Metaspace used 3226K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 355K, capacity 388K, committed 512K, reserved 1048576K
5、空间分配担保
在发生Minor GC之前,虚拟机会检查老年代最大连续可用空间是否大于新生代所有对象的总空间。如果条件成立,则Minor GC是安全的。反之,虚拟机就会查看HandlePromotionFailure是否允许担保失败。允许:再次检查老年代最大可用连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,则进行Minor GC(存在风险);如果小于,或者HandlePromotionFailure参数不允许担保失败,则进行Full GC。
之前提过新生代采用的是复制收集算法,为了提高内存利用率,只使用了其中一个Survivor空间进行备份,且空间分配较小,当大量对象Minor GC后还存活时,就需要老年代进行分代担保。但前提是,老年代有足够的空间区存储新生代存活的对象,而有多少对象会存活在实际内存回收之前是无法明确知道的,所以就以每次回收晋升老年代对象容量的平均大小作为参考,与老年代实际剩余空间进行比较,决定是否进行Full GC使老年代拥有更多的空间。所以会存在风险,毕竟只是个参考,可成功可失败。
注意:JDK1.6 update24之后,HandlePromotionFailure参数不会影响到虚拟机的空间分配担保策略。规则变为只要老年代的连续空间大于新生代总大小或历次晋升的平均大小就会进行Minor GC,否则进行Full GC。