虚拟机采用的是HotSpot内核
对象分配规则
1.对象优先分配在Eden区,如果Eden区没有足够的空间时,虚拟机执行一次Minor GC。
2.大对象直接进入老年代(大对象是指需要大量连续内存空间的对象)。这样做的目的是避免在Eden区和两个Survivor区之间发生大量的内存拷贝。通过参数-XX:PretenureSizeThreshold=3145728控制。
3.长期存活的对象进入老年代。虚拟机为每个对象定义了一个年龄计数器,对象每熬过了1次Minor GC对象的年龄加1,达到阀值对象进入老年区。通过参数-XX:MaxTenuringThreshold=15(默认)控制。
4.动态判断对象的年龄。如果Survivor区中相同年龄的所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象可以直接进入老年代,无需等到MaxTenuringThreshold要求的年龄数。
5.空间分配担保。每次进行Minor GC时,JVM会计算Survivor区移至老年区对象的平均大小,如果这个值大于老年区的剩余值大小则进行一次Full GC,如果小于检查HandlePromotionFailure设置,如果true则只进行Monitor GC,如果false则进行Full GC。 (-XX:-HandlePromotionFailure)
术语说明:
Young Generation(新生代):分为:Eden区和Survivor区,Survivor区有分为大小相等的From Space和To Space。
Old Generation(老年代): 当 OLD 区空间不够时, JVM 会在 OLD 区进行 major collection。
Minor GC:新生代GC,指发生在新生代的垃圾收集动作,因为java对象大多都具备朝生夕死的特性,所以Minor GC非常频繁,一般回收速度也比较快。
Full GC/Major GC:发生老年代的GC,对整个堆进行GC。出现Major GC,经常会伴随至少一次Minor GC(非绝对)。MajorGC的速度一般比minor GC慢10倍以上。
规则一:对象优先在Eden分配
设置虚拟机参数为:-verbose:gc -Xms20M -Xmx20M -Xmn10M
-XX:SurvivorRatio=8 -XX:+PrintGCDetails
虚拟机提供了-XX:+PrintGCDetails 参数打印收集器日志,并且在进程退出时输出当前内存各区域的分配情况。
通过 -Xms20M -Xmx20M -Xmn10M这3个参数限制java堆大小为20MB,且不可扩展。其中10MB分配给新生代,剩下的10MB分配给老年代。
-XX:SurvivorRatio=8 决定了新生代中Eden区与一个Survivor区的比例为8:1,即Eden区=8MB,一个Survivor=1MB,另一个Survivor也为1MB。
/**
* eden 对象通过分配担保机制提前转移到老年代去
* vm 参数 -verbose:gc -Xms20M -Xmx20M -Xmn10M
-XX:SurvivorRatio=8 -XX:+PrintGCDetails
*/
public class MinorGC {
private static final int _1MB=1024*1024;
public static void testMinorGC(){
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]; //出现一次minor GC
}
public static void main(String[] args) {
testMinorGC();
}
}
控制台输出信息:
[GC [DefNew: 6471K->140K(9216K), 0.0074976 secs] 6471K->6284K(19456K), 0.0075433 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Heap
def new generation total 9216K, used 4400K [0x029f0000, 0x033f0000, 0x033f0000)
eden space 8192K, 52% used [0x029f0000, 0x02e18fd8, 0x031f0000)
from space 1024K, 13% used [0x032f0000, 0x03313208, 0x033f0000)
to space 1024K, 0% used [0x031f0000, 0x031f0000, 0x032f0000)
tenured generation total 10240K, used 6144K [0x033f0000, 0x03df0000, 0x03df0000)
the space 10240K, 60% used [0x033f0000, 0x039f0030, 0x039f0200, 0x03df0000)
compacting perm gen total 12288K, used 2105K [0x03df0000, 0x049f0000, 0x07df0000)
the space 12288K, 17% used [0x03df0000, 0x03ffe6e0, 0x03ffe800, 0x049f0000)
No shared spaces configured.
其中“[GC [DefNew:”后面内容为GC收集情况,DefNew为新生代GC
Heap 后面内容为进程退出时输出当前内存各区域的分配情况。
eden space 为eden区
from space 为Survivor区
tenured generation 为年老代
compacting perm gen为永久代(方法区)
日志分析:在执行方法testMinorGC()分配allocation4 对象语句时,会发生Minor GC ,这次GC结果是新生代由6471K变为140K(DefNew: 6471K->140K),而总内存占用量几乎没有减少(allocation1、2、3三个对象都是存活的,虚拟机没有找到可回收的对象)。
这次GC发生的原因是给allocation4分配内存的时候,发现eden已经被占用了6MB,剩余的空间不足以分配allocation4所需的4MB内存,因此发生Minor GC。GC期间虚拟机又发现已有的3个2MB大小的对象全部无法放入Survivor空间(Survivor空间只有1MB大小),所以只好通过分配担保机制提前转移到老年代去。
这次GC结束后,4MB的allocation4对象被顺利分配到eden中。因此程序执行完的结果是eden占用4MB,Survivor空闲,老年代被占用6MB。
规则五:空间分配担保
在发生Minor GC时,虚拟机会检测之前每次晋升到老年代的平均大小是否是否大于老年代剩余空间的大小,如果大于,则改为直接进行一次Full GC。如果小于,则查看HandlePromotionFailure设置是否允许担保失败,如果允许,则只进行Minor GC,如果不允许,则进行一次Full GC。
老年代进行这样的担保,前提是老年代本身还有容纳这些对象的剩余空间,一共有多少对象会活下来,在实际完成内存回收之前是无法明确知道的,所以只好取之前每一次回收晋升到老年达对象容量的平均大小值作为经验值,与老年代的剩余空间进行比较,决定是否进行Full GC来让老年代腾出更多的空间。
如某次Minor GC存活后的对象突增,远高于平均值的话,会导致担保失败。如果出现了HandlePromotionFailure,那就只好在失败后重新发起一次Full GC。
/**
* VM参数:-Xms20M -Xmx20M -Xmn10M -XX:SurvivorRatio=8
-XX:-HandlePromotionFailure -XX:+PrintGCDetails
*/
public class HandlePromotiontest {
private static final int _1MB = 1024 * 1024;
@SuppressWarnings("unused")
public static void testHandlePromotion() {
byte[] allocation1, allocation2, allocation3, allocation4, allocation5, allocation6, allocation7;
allocation1 = new byte[2 * _1MB];
allocation2 = new byte[2 * _1MB];
allocation3 = new byte[2 * _1MB];
allocation1 = null;
allocation4 = new byte[2 * _1MB];//Minor GC
allocation5 = new byte[2 * _1MB];
allocation6 = new byte[2 * _1MB];
allocation4 = null;
allocation5 = null;
allocation6 = null;
allocation7 = new byte[2 * _1MB];//GC
}
public static void main(String[] args) {
testHandlePromotion();
}
}
设置HandlePromotionFailure时
[GC [DefNew: 6471K->140K(9216K), 0.0043721 secs] 6471K->4236K(19456K), 0.0044109 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC [DefNew: 6370K->6370K(9216K), 0.0000361 secs][Tenured: 4096K->4236K(10240K), 0.0045948 secs] 10466K->4236K(19456K), [Perm : 2086K->2086K(12288K)], 0.0047019 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
未设置HandlePromotionFailure时
[GC [DefNew: 6471K->140K(9216K), 0.0042261 secs] 6471K->4236K(19456K), 0.0042664 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC [DefNew: 6370K->140K(9216K), 0.0005921 secs] 10466K->4236K(19456K), 0.0006289 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
日志分析:
设置HandlePromotionFailure参数时:
在执行方法testHandlePromotion()给allocation4 分配对象时,会发生第一次 GC(一般第一次都为Minor GC) ,这次GC发生的原因是给allocation4分配内存的时候,发现eden已经被占用了6MB,剩余的空间不足以分配allocation4所需的2MB内存,因此发生Minor GC。而且已有的3个2MB大小的对象全部无法放入Survivor空间(Survivor空间只有1MB大小),所以只好通过分配担保机制提前转移到老年代去。
第二次GC发生在给allocation7分配2MB对象时,此时eden已经被占用了6MB内存(allocation4、5、6),剩余内存不足需要转移到老年代。因为设置的是不允许担保失败,此时要进行一次Full GC。
注意:第二次GC开始时[GC DefNew: 6370K->6370K,进行minor GC并没有立即执行GC,先判断是否可以空间担保分配,不允许则执行Full GC。
未设置HandlePromotionFailure参数时:
第一次GC同上。
第二次GC发生在给allocation7分配2MB对象时,此时eden已经被占用了6MB内存(allocation4、5、6),剩余内存不足需要转移到老年代。虚拟机检测之前晋升到老年代的平均值(第一分配担保的值4MB即平均值)小于老年代剩余空间的大小。因为设置的是允许担保失败,只需进行一次minor GC。