内存分配与回收策略 - morris131/morris-book GitHub Wiki
Minor GC:指发生在新生代的垃圾收集动作,非常频繁,速度较快。
Major GC:指发生在老年代的GC,出现Major GC,经常会伴随一次Minor GC,同时Minor GC也会引起Major GC,一般在GC日志中统称为GC,不频繁。
Full GC:指发生在老年代和新生代的GC,速度很慢,需要Stop The World。
大多数情况下,对象在新生代Eden区中分配;当Eden区没有足够空间进行分配时,JVM将发起一次Minor GC(新生代GC);Minor GC时,如果发现存活的对象无法全部放入Survivor空间,只好通过分配担保机制提前转移到老年代。
大对象指需要大量连续内存空间的Java对象,如,很长的字符串、数组;经常出现大对象容易导致内存还有不少空间就提前触发GC,以获取足够的连续空间来存放它们,所以应该尽量避免使用创建大对象;"-XX:PretenureSizeThreshold":可以设置这个阈值,大于这个参数值的对象直接在老年代分配;默认为0(无效),且只对Serail和ParNew两款收集器有效。
JVM给每个对象定义一个对象年龄计数器,其计算流程如下:在Eden中分配的对象,经Minor GC后还存活,就复制移动到Survivor区,年龄为1;而后每经一次Minor GC后还存活,在Survivor区复制移动一次,年龄就增加1岁;如果年龄达到一定程度,就晋升到老年代中;"-XX:MaxTenuringThreshold":设置新生代对象晋升老年代的年龄阈值,默认为15;
JVM为更好适应不同程序,不是永远要求等到MaxTenuringThreshold中设置的年龄;如果在Survivor空间中相同年龄的所有对象大小总和大于Survivor空间的一半,大于或等于该年龄的对象就可以直接进入老年代。
分配担保的流程如下:
- 在发生Minor GC前,JVM先检查老年代最大可用的连续空间是否大于新生所有对象空间;
- 如果大于,那可以确保Minor GC是安全的;
- 如果不大于,就继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小;
- 如果大于,将尝试进行一次Minor GC,但这是有风险的;
- 如果小于,那这些也要改为进行一次Full GC;
尝试Minor GC的风险--担保失败:
- 因为尝试Minor GC前面,无法知道存活的对象大小,所以使用历次晋升到老年代对象的平均大小作为经验值;
- 假如尝试的Minor GC最终存活的对象远远高于经验值的话,会导致担保失败(Handle Promotion Failure);
- 失败后只有重新发起一次Full GC,这绕了一个大圈,代价较高;
- 大对象直接进入老年代(-XX:PretenureSizeThreshold)。
- 当对象年龄达到一定的大小 ,就会离开年轻代,进入老年代。
- 当对象首次创建时, 会放在新生代的eden区, GC时survivor无法容纳此对象,进入老年代。
- 如果在Survivor空间中相同年龄的所有对象大小总和大于Survivor空间的一半,大于或等于该年龄的对象就可以直接进入老年代。
有几种进入老年代的情况就有几种Full GC的情况。
- 调用System.gc时,系统建议执行Full GC,但是不必然执行
- 老年代空间不足
- 方法去空间不足
- 通过Minor GC后进入老年代的平均大小大于老年代的可用内存
- 由Eden区、From Space区向To Space区复制时,对象大小大于To Space可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小