代码
public class EdenAllocation1 {//Eden分配
private static final int _1MB = 1024*1024;
@SuppressWarnings("unused")
public static void main(String[] args) {
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];
}
}
日志
[GC[DefNew: 6988K->484K(9216K), 0.0104762 secs] 6988K->6628K(19456K), 0.0274380 secs]
[Times: user=0.00 sys=0.00, real=0.03 secs]
Heap
def new generation total 9216K, used 4910K [0x00000000f9a00000, 0x00000000fa400000, 0x00000000fa400000)
eden space 8192K, 54% used [0x00000000f9a00000, 0x00000000f9e527b0, 0x00000000fa200000)
from space 1024K, 47% used [0x00000000fa300000, 0x00000000fa3791b8, 0x00000000fa400000)
to space 1024K, 0% used [0x00000000fa200000, 0x00000000fa200000, 0x00000000fa300000)
tenured generation total 10240K, used 6144K [0x00000000fa400000, 0x00000000fae00000, 0x00000000fae00000)
the space 10240K, 60% used [0x00000000fa400000, 0x00000000faa00030, 0x00000000faa00200, 0x00000000fae00000)
compacting perm gen total 21248K, used 2541K [0x00000000fae00000, 0x00000000fc2c0000, 0x0000000100000000)
the space 21248K, 11% used [0x00000000fae00000, 0x00000000fb07b6a0, 0x00000000fb07b800, 0x00000000fc2c0000)
No shared spaces configured.
分析
JVM配置:不可扩展堆内存20MB,其中新生代10MB,老年代10MB,而新生代区域的分配比例是8:1:1,使用Serial/Serial Old组合收集器。
从代码可以看出,allocation1、allocation2、allocation3一共需要6MB,而Eden一共有8MB,优先分配到Eden。但再分配allocation4的时候Eden空间不够(allocation1、allocation2、allocation3都是存活的,虚拟机几乎没有找到可回收的对象),执行了一次Minor GC,在GC中,由于Survivor只有1MB,不够存放allocation1、allocation2、allocation3,所以直接迁移到老年代了(分配担保),老年代增加6M,最后Eden空闲出来了就可以放allocation4,消耗内存4M。
2验证大对象直接进入老年代大对象:需要大量连续内存空间的Java对象,最典型的大对象就是那种很长的字符串以及数组。大对象对虚拟机的内存分配来说就是一个坏消息(替Java虚拟机抱怨一句,比遇到一个大对象更加坏的消息就是遇到一群“朝生夕灭”的“短命大对象”,写程序的时候应当避免),经常出现大对象容易导致内存还有不少空间时就提前触发垃圾收集以获取足够的连续空间来“安置”它们。
参数:-XX:PretenureSizeThreshold:令大于这个设置值的对象直接在老年代分配,避免在Eden区及两个Survivor区之间发生大量的内存复制.
代码
package com.fei.zhou.day1;
public class PretenureSizeAllocation {
private static final int _1MB = 1024*1024;
@SuppressWarnings("unused")
public static void main(String[] args) {
byte[] allocation1;
allocation1 = new byte[4 * _1MB];
}
}
结果:
Heap
def new generation total 9216K, used 1010K [0x00000000f9a00000, 0x00000000fa400000, 0x00000000fa400000)
eden space 8192K, 12% used [0x00000000f9a00000, 0x00000000f9afc938, 0x00000000fa200000)
from space 1024K, 0% used [0x00000000fa200000, 0x00000000fa200000, 0x00000000fa300000)
to space 1024K, 0% used [0x00000000fa300000, 0x00000000fa300000, 0x00000000fa400000)
tenured generation total 10240K, used 4096K [0x00000000fa400000, 0x00000000fae00000, 0x00000000fae00000)
the space 10240K, 40% used [0x00000000fa400000, 0x00000000fa800010, 0x00000000fa800200, 0x00000000fae00000)
compacting perm gen total 21248K, used 2538K [0x00000000fae00000, 0x00000000fc2c0000, 0x0000000100000000)
the space 21248K, 11% used [0x00000000fae00000, 0x00000000fb07aac0, 0x00000000fb07ac00, 0x00000000fc2c0000)
No shared spaces configured.
分析:
eden区没有被使用,老年代使用了4M,内存分配的时候直接给老年代,因为我们配置参数,大于3M直接进老年代。
3长期存活的对象将进入老年代public class Test {
private static final int _1MB = 1024 * 1024;
//参数
//-verbose:gc
//-Xms20M
//-Xmx20M
//-Xmn10M
//-XX:+PrintGCDetails
//-XX:SurvivorRatio=8
//-XX:MaxTenuringThreshold=1
//-XX:+PrintTenuringDistribution
//-XX:+UseSerialGC
public static void main(String[] args) {
byte[] allocation1, allocation2, allocation3;
allocation1 = new byte[_1MB / 4];
// 什么时候进入老年代取决于XX:MaxTenuringThreshold设置
allocation2 = new byte[4 * _1MB];
allocation3 = new byte[4 * _1MB];
allocation3 = null;
allocation3 = new byte[4 * _1MB];
}
}
对象年龄为1的日志:
[GC[DefNew
Desired survivor size 524288 bytes, new threshold 1 (max 1)
- age 1: 740920 bytes, 740920 total
: 5187K->723K(9216K), 0.0069456 secs] 5187K->4819K(19456K), 0.0070119 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
[GC[DefNew
Desired survivor size 524288 bytes, new threshold 1 (max 1)
- age 1: 120 bytes, 120 total
: 4983K->0K(9216K), 0.0021117 secs] 9079K->4819K(19456K), 0.0021468 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Heap
def new generation total 9216K, used 4344K [0x00000000f9a00000, 0x00000000fa400000, 0x00000000fa400000)
eden space 8192K, 53% used [0x00000000f9a00000, 0x00000000f9e3e238, 0x00000000fa200000)
from space 1024K, 0% used [0x00000000fa200000, 0x00000000fa200078, 0x00000000fa300000)
to space 1024K, 0% used [0x00000000fa300000, 0x00000000fa300000, 0x00000000fa400000)
tenured generation total 10240K, used 4819K [0x00000000fa400000, 0x00000000fae00000, 0x00000000fae00000)
the space 10240K, 47% used [0x00000000fa400000, 0x00000000fa8b4e48, 0x00000000fa8b5000, 0x00000000fae00000)
compacting perm gen total 21248K, used 2540K [0x00000000fae00000, 0x00000000fc2c0000, 0x0000000100000000)
the space 21248K, 11% used [0x00000000fae00000, 0x00000000fb07b340, 0x00000000fb07b400, 0x00000000fc2c0000)
No shared spaces configured.
分析
执行01,02
eden区:allocation1和allocation2进来,总大小4.25M
执行03
发现4M+4.25M>eden(8M),发生GC,把allocation1放到from(1M,age=1),将allocation2放到old(10M)
同时将allocation3放到eden
执行04
告诉GC,allocation3是内存空间是没有引用链,是可回收的空间(现在没有回收,在占有eden内存4M)
执行05
发现4M+4M>=eden(8M),发生GC,把没有引用链的4M内存清空,设置allocation1的age=2,同时将from区allocation1移到old(年龄>1)。
将新的allocation3放到eden区。
eden(8M) [from(1M) age] [to(1M) age] old(10M)
步骤01 allocation1 = new byte[_1MB / 4]; 0.25
步骤02 allocation2 = new byte[4 * _1MB]; 4.25
步骤03 allocation3 = new byte[4 * _1MB]; GC 4 0.25 1 4M
步骤04 allocation3 = null;
步骤05 allocation3 = new byte[4 * _1MB]; GC 4 4.25
年龄为15的测试
4动态对象年龄判定年龄15的日志
public class Test {
private static final int _1MB = 1024 * 1024;//参数
//-verbose:gc
//-Xms20M
//-Xmx20M
//-Xmn10M
//-XX:+PrintGCDetails
//-XX:SurvivorRatio=8
//-XX:MaxTenuringThreshold=15
//-XX:+PrintTenuringDistribution
//-XX:+UseSerialGC
public static void main(String[] args) {
byte[] allocation1, allocation2, allocation3;
allocation1 = new byte[_1MB /33];
// 什么时候进入老年代取决于XX:MaxTenuringThreshold设置
allocation2 = new byte[4 * _1MB];
allocation3 = new byte[4 * _1MB];
allocation3 = null;
allocation3 = new byte[4 * _1MB];
}
}结果:
[GC[DefNew
Desired survivor size 524288 bytes, new threshold 15 (max 15)
- age 1: 510552 bytes, 510552 total
: 4767K->498K(9216K), 0.0096596 secs] 4767K->4594K(19456K), 0.0097361 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
[GC[DefNew
Desired survivor size 524288 bytes, new threshold 15 (max 15)
- age 1: 120 bytes, 120 total
- age 2: 510552 bytes, 510672 total
: 4758K->498K(9216K), 0.0024039 secs] 8854K->4594K(19456K), 0.0024478 secs] [Times: user=0.02 sys=0.00, real=0.00 secs]
Heap
def new generation total 9216K, used 4922K [0x00000000f9a00000, 0x00000000fa400000, 0x00000000fa400000)
eden space 8192K, 54% used [0x00000000f9a00000, 0x00000000f9e51f90, 0x00000000fa200000)
from space 1024K, 48% used [0x00000000fa200000, 0x00000000fa27cad0, 0x00000000fa300000)
to space 1024K, 0% used [0x00000000fa300000, 0x00000000fa300000, 0x00000000fa400000)
tenured generation total 10240K, used 4096K [0x00000000fa400000, 0x00000000fae00000, 0x00000000fae00000)
the space 10240K, 40% used [0x00000000fa400000, 0x00000000fa800010, 0x00000000fa800200, 0x00000000fae00000)
compacting perm gen total 21248K, used 2540K [0x00000000fae00000, 0x00000000fc2c0000, 0x0000000100000000)
the space 21248K, 11% used [0x00000000fae00000, 0x00000000fb07b338, 0x00000000fb07b400, 0x00000000fc2c0000)
No shared spaces configured.
如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无须等到MaxTenuringThreshold中要求的年龄。
public class Test {
private static final int _1MB = 1024 * 1024;
//参数
//-verbose:gc
//-Xms20M
//-Xmx20M
//-Xmn10M
//-XX:+PrintGCDetails
//-XX:SurvivorRatio=8
//-XX:MaxTenuringThreshold=15
//-XX:+PrintTenuringDistribution
//-XX:+UseSerialGC
public static void main(String[] args) {
byte[] allocation1, allocation2, allocation3, allocation4;
allocation1 = new byte[_1MB / 4];
allocation2 = new byte[_1MB / 4];
// allocation1+allocation2大于survivor空间的一半 ,进入老年代
allocation3 = new byte[4 * _1MB];
allocation4 = new byte[4 * _1MB];
allocation4 = null;
allocation4 = new byte[4 * _1MB];
}
}
结果:
[GC[DefNew
Desired survivor size 524288 bytes, new threshold 1 (max 15)
- age 1: 1003080 bytes, 1003080 total
: 5279K->979K(9216K), 0.0055052 secs] 5279K->5075K(19456K), 0.0055649 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC[DefNew
Desired survivor size 524288 bytes, new threshold 15 (max 15)
- age 1: 256 bytes, 256 total
: 5403K->0K(9216K), 0.0024450 secs] 9499K->5075K(19456K), 0.0024776 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Heap
def new generation total 9216K, used 4260K [0x00000000f9a00000, 0x00000000fa400000, 0x00000000fa400000)
eden space 8192K, 52% used [0x00000000f9a00000, 0x00000000f9e28fd0, 0x00000000fa200000)
from space 1024K, 0% used [0x00000000fa200000, 0x00000000fa200100, 0x00000000fa300000)
to space 1024K, 0% used [0x00000000fa300000, 0x00000000fa300000, 0x00000000fa400000)
tenured generation total 10240K, used 5075K [0x00000000fa400000, 0x00000000fae00000, 0x00000000fae00000)
the space 10240K, 49% used [0x00000000fa400000, 0x00000000fa8f4c10, 0x00000000fa8f4e00, 0x00000000fae00000)
compacting perm gen total 21248K, used 2540K [0x00000000fae00000, 0x00000000fc2c0000, 0x0000000100000000)
the space 21248K, 11% used [0x00000000fae00000, 0x00000000fb07b358, 0x00000000fb07b400, 0x00000000fc2c0000)
No shared spaces configured.
分析
即使MaxTenuringThreshold=15,会发现运行结果中Survivor的空间占用仍然为0%,而老年代比预期增加了9%,也就是说allocation1、allocation2对象都直接进入了老年代,而没有等到15岁的临界年龄。因为这两个对象加起来已经达到了0.5M,并且它们是同年的,满足同年对象达到Survivor空间的一半规则。我们只要注释掉其中一个对象的new操作,就会发现另外一个不会晋升到老年代中去。
5空间分配担保JDK 6 Update 24之前规则
在发生Minor GC时,虚拟机会检测之前每次晋升到老年代的平均大小是否大于老年代的剩余空间大小,如果大于,则改为直接进行一次Full GC。如果小于,则查看HandlePromotionFailure设置是否允许担保失败;如果允许,那只会进行Minor GC;如果不允许,则也要改为进行一次Full GC。空间分配担保
前面提到过,新生代使用复制收集算法,但为了内存利用率,只使用其中一个Survivor空间来作为轮换备份,因此当出现大量对象在Minor GC后仍然存活的情况时(最极端就是内存回收后新生代中所有对象都存活),就需要老年代进行分配担保,让Survivor无法容纳的对象直接进入老年代。与生活中的贷款担保类似,老年代要进行这样的担保,前提是老年代本身还有容纳这些对象的剩余空间,一共有多少对象会活下来,在实际完成内存回收之前是无法明确知道的,所以只好取之前每一次回收晋升到老年代对象容量的平均大小值作为经验值,与老年代的剩余空间进行比较,决定是否进行Full GC来让老年代腾出更多空间。
取平均值进行比较其实仍然是一种动态概率的手段,也就是说如果某次Minor GC存活后的对象突增,远远高于平均值的话,依然会导致担保失败(Handle Promotion Failure)。如果出现了HandlePromotionFailure失败,那就只好在失败后重新发起一次Full GC。虽然担保失败时绕的圈子是最大的,但大部分情况下都还是会将HandlePromotionFailure开关打开,避免Full GC过于频繁,
JDK 6 Update 24之后的规则:只要老年代的连续空间大于新生代对象总大小或者历次晋升的平均大小就会进行Minor GC,否则将进行Full GC。
测试jdk7
public class Test {
private static final int _1MB = 1024 * 1024;
//参数
//-verbose:gc
//-Xms20M
//-Xmx20M
//-Xmn10M
//-XX:+PrintGCDetails
//-XX:SurvivorRatio=8
//-XX:MaxTenuringThreshold=15
//-XX:+PrintTenuringDistribution
//-XX:+UseSerialGC
public static void main(String[] args) {
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];
}
}
日志
Java HotSpot(TM) 64-Bit Server VM warning: ignoring option HandlePromotionFailure; support was removed in 6.0_24
[GC[DefNew
Desired survivor size 524288 bytes, new threshold 15 (max 15)
- age 1: 478760 bytes, 478760 total
: 6815K->467K(9216K), 0.0091874 secs] 6815K->6611K(19456K), 0.0092695 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
Heap
def new generation total 9216K, used 5055K [0x00000000f9a00000, 0x00000000fa400000, 0x00000000fa400000)
eden space 8192K, 56% used [0x00000000f9a00000, 0x00000000f9e7af60, 0x00000000fa200000)
from space 1024K, 45% used [0x00000000fa300000, 0x00000000fa374e28, 0x00000000fa400000)
to space 1024K, 0% used [0x00000000fa200000, 0x00000000fa200000, 0x00000000fa300000)
tenured generation total 10240K, used 6144K [0x00000000fa400000, 0x00000000fae00000, 0x00000000fae00000)
the space 10240K, 60% used [0x00000000fa400000, 0x00000000faa00030, 0x00000000faa00200, 0x00000000fae00000)
compacting perm gen total 21248K, used 2540K [0x00000000fae00000, 0x00000000fc2c0000, 0x0000000100000000)
the space 21248K, 11% used [0x00000000fae00000, 0x00000000fb07b350, 0x00000000fb07b400, 0x00000000fc2c0000)
No shared spaces configured.



