JVM学习笔记(十、GC3-垃圾回收机制)
目录:
- 几种GC介绍
- 对象优先分配到Eden区
- 大对象直接进入老年代
- 长期存活的对象会进入老年代
- 空间担保
- GC日志解读
几种GC介绍:
- Minor GC:从年轻代空间(包括 Eden 和 Survivor 区域)回收内存 。
- Major GC:是清理永久代。
- Full GC:是清理整个堆空间—包括年轻代和永久代。
1、Minor GC:
指发生在新生代的垃圾收集动作,因为Java对象大多都具备朝生夕灭的特性,所以Minor GC非常频繁,一般回收速度也比较快。
MinorGC用于清理新生代,MajorGC清理老年代。FullGC清理新生代+老年代(方法区)
MinorGC触发条件:当新生代无法给新对象分配空间时。如:Eden区满了。
2、Major GC/Full GC:
指发生在老年代的GC,出现了Major GC,经常会伴随至少一次的Minor GC(但非绝对的,在Parallel Scavenge收集器的收集策略里就有直接进行Major GC的策略选择过程)。Major GC的速度一般会比Minor GC慢10倍以上。
MajorGC触发条件:老年代没有空间。
Full GC本身不会先进行Minor GC,可以配置Full GC之前先进行一次Minor GC,因为老年代很多对象都会引用到新生代的对象,先进行一次Minor GC可以提高老年代GC的速度。比如老年代使用CMS时,设置CMSScavengeBeforeRemark优化,可以让CMS remark之前先进行一次Minor GC。
Full GC 触发条件:
- System.gc
- 老年代空间不足
- 方法区空间不足
- 经过MinorGC后,进行的对象大小大于老年代的可用空间。如:Eden+From到To复制的时候,对象的大小大于To区域大小,当把对象转到老年,这个时候老年代的空间小于对象大小。
对象优先分配到Eden区:
大多数情况下,对象在新生代Eden区中分配。 当Eden区没有足够空间进行分配时,虚拟机将发起一次Minor GC。
1 public class TestPolicy1 { 2 3 private static final int _1MB = 1024 * 1024; 4 5 /** 6 * 使用-XX:+PrintGCDetails打印 7 */ 8 public static void testAllocation() { 9 byte[] allocation1, allocation2, allocation3, allocation4; 10 allocation1 = new byte[2 * _1MB]; 11 allocation2 = new byte[2 * _1MB]; 12 allocation3 = new byte[2 * _1MB]; 13 allocation4 = new byte[4 * _1MB]; 14 } 15 16 public static void main(String[] args) { 17 testAllocation(); 18 } 19 20 }
运行后可发现仅eden区有数据。
大对象直接进入老年代:
所谓的大对象是指,需要大量连续内存空间的Java对象,最典型的大对象就是那种很长的字符串以及数组(比遇到大对象更坏的就是一群“朝生夕灭”的“短命大对象”,写程序的时候应当避免)。
经常出现大对象容易导致内存还有不少空间时就提前触发GC以获取足够的连续空间来“安置”它们。
1 public class TestPolicy2 { 2 3 private static final int _1MB = 1024 * 1024; 4 5 /** 6 * -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 7 * -XX:PretenureSizeThreshold=3145728 设置对象直接进入老年代的阈值,默认是0 8 */ 9 public static void testPretenureSizeThreshold() { 10 byte[] allocation = new byte[8 * _1MB]; 11 } 12 13 public static void main(String[] args) { 14 testPretenureSizeThreshold(); 15 } 16 17 }
8M内存是大对象,所以直接进入老年代:
注意:
- PretenureSizeThreshold参数只对Serial和ParNew两款收集器有效,Parallel Scavenge收集器不认识这个参数,Parallel Scavenge收集器一般并不需要设置。
- 如果遇到必须使用此参数的场合,可以考虑ParNew加CMS的收集器组合。
长期存活的对象会进入老年代:
虚拟机给每个对象定义了一个对象年龄(Age)计数器。如果对象在Eden出生并经过第一次Minor GC后仍然存活,并且能被Survivor容纳的话,将被移动到Survivor空间中,并且对象年龄设为1。
对象在Survivor区中每“熬过”一次Minor GC,年龄就增加1岁,当它的年龄增加到一定程度(默认为15),就将会被晋升到老年代中。对象晋升老年代的年龄阈值,可以通过参数-XX:MaxTenuringThreshold设置。
1 public class TestPolicy3 { 2 3 private static final int _1MB = 1024 * 1024; 4 5 /** 6 * -verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:SurvivorRatio=8 -XX:TargetSurvivorRatio=90 -XX:+PrintGCDetails 7 * -XX:MaxTenuringThreshold=1 存活次数 8 * -XX:+PrintTenuringDistribution 9 */ 10 public static void testTenuringThreshold() { 11 byte[] allocation1, allocation2, allocation3; 12 allocation1 = new byte[_1MB / 4]; 13 allocation2 = new byte[4 * _1MB]; 14 allocation3 = new byte[4 * _1MB]; 15 System.out.println("===================="); 16 allocation3 = null; 17 allocation3 = new byte[4 * _1MB]; 18 System.out.println("===================="); 19 } 20 21 public static void main(String[] args) { 22 testTenuringThreshold(); 23 } 24 25 }
虚拟机并不是永远要求对象的年龄必须达到了MaxTenuringThreshold才能晋升到老年代。动态年龄判断基于如下两点:
- 如果在Survivor空间中相同年龄的所有对象大小的总和大于Survivor空间的一半。
- 年龄大于或者等于Survivor空间中平均年龄就可以直接进入老年代。
注意:动态对象年龄判断和TargetSurvivorRatio有很大关系。该参数的含义是:根据年龄从小到大累加对象的字节大小,如果累加值超过Survivor区域大小*TargetSurvivorRatio(默认50%),则这个年龄之上的对象都要晋升到老年代。
空间担保:
在发生Minor GC之前,虚拟机会先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果这个条件成立,那么Minor GC可以确保是安全的。如果不成立,则虚拟机会查看HandlePromotionFailure设置值是否允许担保失败。
如果允许,那么会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,将尝试着进行一次Minor GC,尽管这次Minor GC是有风险的;如果小于,或者HandlePromotionFailure设置不允许冒险,那这时也要改为进行一次Full GC。
注意:在Jdk1.6 update 24之后-XX:-HandlePromotionFailure 不再起作用,只要老年代的连续空间大于新生代对象的总大小或者历次晋升到老年代的对象的平均大小就执行MinorGC,否则执行FullGC。
GC日志解读:
[GC (Allocation Failure) [DefNew: 6518K->898K(9216K), 0.0038707 secs] 6518K->4994K(19456K), 0.0042538 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
- GC (Allocation Failure):GC的类型,括号里面是GC产生的原因。
- DefNew:垃圾回收期的名称。
- 6518K->898K(9216K):6518K代表GC之前大小,898K代表GC之后的大小。9216K总的大小。
- 0.0038707 secs:GC暂停的时间。
- 6518K->4994K(19456K):个区域的情况。
- [Times: user=0.00 sys=0.00, real=0.00 secs]
- user:执行GC期间线程消耗的总CPU时间
- sys:操作系统调用或者等待系统事件等待的时间
- real:应用程序停止的时间