新建的对象都先放在新生代的 Eden 区 和 Survivor 1 区,两个区都满了后触发 GC,把存活对象转移到 Survivor 2 区,然后使用清空的 Eden + Survivor2 存放新对象。
1. 那新生代的存活对象,在什么样的情况下进入老年代呢?
(1) 躲过15次 GC 之后进入老年代
JVM 参数 -XX:MaxTenuringThreshold 可以设置多少岁进入老年代,默认是15岁;
(2) 动态对象年龄规则判断进入老年代
年龄1+年龄2+…+年龄n的多个年龄对象大小的总和 > Survivor 区的50%,就会把年龄n以上的对象都放入老年代;
以上两个规则的目的,都是希望将那些可能长期存活的对象,尽早进入老年代。
(3) 大对象直接进入老年代
JVM 参数 -XX:PretenureSizeThreshold 可以设置一个单位是字节的值,如果要创建的对象大于这个值,直接进入老年代,不会经过新生代;
新生代 Eden 发生垃圾回收后,发现存活对象大小超过了新生代空间的 10%,即 Survivor 的大小放不下这些对象,则直接转移到老年代去;
2. 如果新生代有大量的存活对象,Survivor 区放不下,老年代区也放不下,怎么办?
- 首先,新生代里执行任何一次 Minor GC 之前,考虑到极端情况:所有对象都存活的,就需要检查一下老年代可用的内存空间,是否能放下新生代所有的对象:
- 老年代空间大于所有对象的总和,如果 GC 后存活对象放不下 Survivor,也可以放到老年代里;
- 老年代空间小于所有对象的总和,检查 -XX:-HandlePromotionFailure 是否设置:
- 没有设置,触发 Full GC 对老年代垃圾回收,一般会同时伴随着 Minor GC;
- 设置了,检查老年代的内存空间是否大于历次 Minor GC 转移进入老年代的对象平均大小:
- 小于,触发 Full GC 对老年代垃圾回收,一般会同时伴随着 Minor GC;
- 大于,执行一次 Minor GC,可能有几种结果:
- 存活对象大小很小,可以转移到 Survivor;
- 存活对象大小大于 Survivor,小于老年代,转移到老年代;
- 存活对象大小很大,老年代也放不下,触发 Full GC 再次清理老年代,同时伴随着 Minor GC;
- 垃圾清理后,情况好转,可以放下存活对象;
- 情况仍未好转,老年代还是放不下存活对象,导致 OOM,内存溢出。
3. 老年代的垃圾回收算法是怎样的?
- 标记整理算法:
- 首先标记出老年代当前存活的对象,这些对象的空间可能是碎片化的,不连续;
- 尽量移动这些存活对象,让它们紧凑的靠在一起,避免垃圾回收后内存碎片太多;
- 清理老年代的垃圾对象;
- 老年代的垃圾回收算法比新生代的,速度至少慢10倍,如果频繁 Full GC,系统性能会被严重影响,频繁出现卡顿;