GC就是Java的垃圾回收机制,要了解什么情况下会发生GC(即GC得触发条件),我们需要先了解JVM的内存模型结构,之前一篇文章已经详细讲解了Jvm的内存模型结构,而通常来说,GC主要针对的是堆(java heap)区。
而java heap是分代的(年轻代和老年代),为什么要分代?其实也不难理解,分代就是为了优化性能,如果不分代,那就会导致所有对象揉在一块,那样GC就会对堆区域进行全扫描。所以,分代可以大大提升GC性能,那么,分代的原理是什么?
JVM对于堆的垃圾回收,采用分代收集的策略,所以分代的原理就是根据堆中对象的存活周期进行分代,年轻代中,每次垃圾回收都有大批对象死去,只有少量存活,而老年代中存放的对象存活率高。
>>>>>必须知道的知识点<<<<<
Young space&#xff1a;年轻代(新生代)&#xff0c;保存生命周期较短的对象
Tenured space&#xff1a;老年代(年老代)&#xff0c;保存生命周期较长的对象
Minor GC&#xff1a;发生在Young space中的gc
Major GC&#xff1a;发生在老年代Tenured space中的gc
STW(stop the world)&#xff1a;指的是用户线程在运行至安全点(safe point)或安全区域(safe region)之后&#xff0c;就自行挂起&#xff0c;进入暂停状态&#xff0c;对外的表现就是卡顿&#xff0c;而不论何种gc算法&#xff0c;不论是minor gc还是major gc都会STW&#xff0c;区别只在于STW的时间长短。
Full GC&#xff1a;无官方定义&#xff0c;通常意义上而言指的是一次特殊GC的行为描述&#xff0c;这次GC会回收整个堆的内存&#xff0c;包含老年代&#xff0c;新生代&#xff0c;metaspace等。
但是实际情况中&#xff0c;我们主要看的是gc.log日志&#xff0c;其中也会发现在部分gc日志头中也有Full GC字眼&#xff0c;此处表示含义是在这次GC的全过程中&#xff0c;都是STW的状态&#xff0c;也就是说在这次GC的全过程中所有用户线程都是处于暂停的状态。
>>>>>年轻代<<<<<
Jvm把年轻代分三部分&#xff1a;1个Eden(伊甸园)区和2个Survivor(幸存者)区(分别叫from和to)&#xff0c;默认比例为8:1。
为啥默认这个比例&#xff1f;
一般情况下&#xff0c;新创建的对象都会被分配到Eden区(一些大对象特殊处理),这些对象经过第一次Minor
GC后&#xff0c;如果仍然存活&#xff0c;将会被移到Survivor区。对象在Survivor区中每熬过一次Minor
GC&#xff0c;年龄就会增加1岁&#xff0c;当它的年龄增加到一定程度(默认15岁)时&#xff0c;就会被移动到年老代中。
因为年轻代中的对象基本都是朝生夕死的(80%以上)&#xff0c;所以年轻代的垃圾回收算法采用复制算法(内存分为两块&#xff0c;每次只用其中一块&#xff0c;当一块内存用完&#xff0c;就将还活着的对象复制到另外一块内存上&#xff0c;复制算法不产生内存碎片)。在GC开始的时候&#xff0c;对象只会存在于Eden区和名为“From”的Survivor区&#xff0c;Survivor区“To”是空的。紧接着进行GC&#xff0c;Eden区中所有存活的对象都会被复制到“To”&#xff0c;而在“From”区中&#xff0c;仍存活的对象会根据他们的年龄值来决定去向。年龄达到一定值(年龄阈值&#xff0c;可以通过-XX:MaxTenuringThreshold来设置)的对象会被移动到年老代中&#xff0c;没有达到阈值的对象会被复制到“To”区域。经过这次GC后&#xff0c;Eden区和From区已经被清空。
这个时候&#xff0c;“From”和“To”会交换他们的角色&#xff0c;就是新的“To”是上次GC前的“From”&#xff0c;新的“From”就是上次GC前的“To”。不管怎样&#xff0c;都会保证名为To的Survivor区域是空的。Minor
GC会一直重复这样的过程&#xff0c;直到“To”区被填满&#xff0c;“To”区被填满之后&#xff0c;会将所有对象移动到年老代中。
总结下来&#xff0c;JVM的堆区对象分配的规则一般如下&#xff1a;
1)对象优先在Eden区分配
2)大对象直接进入老年代(-XX:PretenureSizeThreshold&#61;3145728 该参数来定义进入老年代对象大小)
3)长期存活的对象将进入老年代(在JDK8中-XX:MaxTenuringThreshold&#61;1的阀值设定根本没用)
4)动态对象年龄判定(虚拟机并不会永远地要求对象的年龄都必须达到MaxTenuringThreshold才能晋升老年代&#xff0c;如果Survivor空间中相同年龄的所有对象的大小总和大于Survivor的一半&#xff0c;年龄大于或等于该年龄的对象就可以直接进入老年代)
5)空间分配担保
6)只要老年代的连续空间大于(新生代所有对象的总大小或者历次晋升的平均大小)就会进行minor GC&#xff0c;否则会进行full GC
GC的触发条件
PS:JVM优化的目的就是减少SWT执行的时间(避免卡顿)&#xff0c;避免频繁full gc
1)System.gc()方法的调用。
此方法的调用是建议JVM进行Full GC,虽然只是建议而非一定&#xff0c;但很多情况下它会触发 Full GC,从而增加Full
GC的频率&#xff0c;也即增加了间歇性停顿的次数。强烈影响系建议能不使用此方法就别使用&#xff0c;让虚拟机自己去管理它的内存&#xff0c;可通过通过-XX:&#43;
DisableExplicitGC来禁止RMI(Java远程方法调用)调用System.gc。
2)旧生代空间不足。旧生代空间只有在新生代对象转入及创建为大对象、大数组时才会出现不足的现象&#xff0c;当执行Full
GC后空间仍然不足&#xff0c;则抛出错误&#xff1a;java.lang.OutOfMemoryError: Java heap space
。为避免以上两种状况引起的FullGC&#xff0c;调优时应尽量做到让对象在Minor
GC阶段被回收、让对象在新生代多存活一段时间及不要创建过大的对象及数组。
3)Permanet Generation空间满了。Permanet
Generation中存放的为一些class的信息等&#xff0c;当系统中要加载的类、反射的类和调用的方法较多时&#xff0c;Permanet
Generation可能会被占满&#xff0c;在未配置为采用CMS GC的情况下会执行Full GC。如果经过Full
GC仍然回收不了&#xff0c;那么JVM会抛出错误信息&#xff1a;java.lang.OutOfMemoryError: PermGen space 。为避免Perm
Gen占满造成Full GC现象&#xff0c;可采用的方法为增大Perm Gen空间或转为使用CMS GC。
4)通过Minor GC后进入老年代的平均大小大于老年代的可用内存
5)由Eden区、From Space区向To Space区复制时&#xff0c;对象大小大于To Space可用内存&#xff0c;则把该对象转存到老年代&#xff0c;且老年代可用内存不足(老年代可用内存小于该对象)