堆内存分配
对于堆内存,很多垃圾回收器都会按照以下的策略分配新生代的空间。
新的对象会优先在 Eden 区,第1次进行垃圾回收的时候,将存活下来的对象放到 From Survivor 区,然后对 Eden 区进行清理。当 Eden 区再次满了的时候,就会将 Eden 区和 From Survivor 区存活的对象转移到 To Survivor 区,然后将 Eden 区和 From Survivor 区清理掉。如此循环清理,来提升内存的使用率。
对象什么时候进入老年代?
1. 到达年龄进入老年代
每进行一次新生代回收,对象的年龄就会增加1,达到15岁的对象,也就是经历过15次新生代回收都没有被清理掉的话,就会被移入老年代。
2. 动态对象年龄判断机制
对象不是一定要到15岁或者参数( -XX:MaxTenuringThreshold)指定的年龄才会进入老年代的。年龄1 + 年龄2 + 年龄n 的多个年龄对象综合,超过 Survivor 区容量的一半(默认一半由参数 TargetSurvivorRatio 配置),那么这个年龄以及这个年龄以上的对象就会进入老年代。
例如,年龄1的对象占了 S 区的 25%,年龄2的对象占了 20% ,年龄3 的对象占了 35%。那么新生代进行垃圾回收的时候,就会发现年龄2 + 年龄3 的对象占了55%。 那么年龄2 和年龄3 的对象就会进入到老年代。
关于这个机制,有个疑问,会不会导致,S区总是使用率低于50%?实际上是这样吗?
3. 大对象直接进入老年代
可以通过参数 -XX:PretenureSizeThreshold 配置,超过设定值的对象,直接进入老年代。这个机制是因为要避免频繁操作大对象,将对象在 Survivor 区来回复制。
这个参数值对 Serial 和 ParNew 这两款新生代收集器有效,如果必须要使用这个参数进行调优,可以考虑 ParNew + CMS 的收集器组合。
4. 大量存活对象直接进入老年代
如果对 Eden 区进行垃圾回收后,发现存活下来的对象超过 Survivor 区的容量,无法放入 Survivor 区,那么这些对象就会直接进入老年代。 只是 Eden 区存活的对象? 还是这次回收存活下来的对象(包含 S 区存活下来的对象) 。 是全部进入老年代 , 还是只是一部分进入老年代?
空间担保机制
在进行 Minor GC 之前都会检查老年代的可用内存空间,是否可以装下新生代所有对象的总大小。在极端的情况下,新生代全部对象都存活。
检查老年代时候,如果发现老年代的剩余空间可以存下新生代的所有对象,那么就可以正常进行 Minor GC。否则的话,就会去检查参数 -XX:-HandlePromotionFailure 。如果这个参数开启了,就会去检查老年代剩余空间的大小是否大于之前每次 Minor GC 后进入老年代的对象的平均大小。 例如每次 Minor GC 后进入老年代的对象的平均大小是 100M,就会检查老年代剩余空间是否大于 100M。相当于预测可能这次进入老年代的对象也是 100M。
如果老年代剩余空间小于这次估计的 100M ,或者是这个参数没有设置。那么就会触发一次 Full GC,对老年代进行回收,清理出老年代的空间。然后再执行 Minor GC。
如果老年代剩余空间大于这次估计的 100M,那么就会直接进行 Minor GC,进行回收后,会出现以下情况。
- Minor GC 后的存活对象小于 Survivor 的剩余空间,就直接进入 Survivor 区。情况比较良好。
- Minor GC 后的存活对象大于 Survivor 的剩余空间,老年代的可以装得下存活的对象,那么就会直接进入老年代。
- Minor GC 后的存活对象,Survivor 剩余空间装不下,老年代剩余空间也装不下。那么就会触发一次 Full GC,进行老年代空间回收。Full GC 后,老年代的剩余空间还是放不下存活的对象,那么就会报内存溢出错误,也就是 OOM,因为内存确实不够了。
GC 的触发时机
根据上面分析的新生代和老年代机制,得出一般情况下 GC 的触发时机。
什么时候 触发 Young GC?
新生代的分配内存差不多满了,继续申请内存创建对象,发现内存不够的时候,就会触发新生代的回收。
什么时候 触发 Full GC?
一般来说是2个时机:
- 新生代回收发生前,检查老年代剩余的空间是否可以容纳可能进入老年代的存活对象,如果空间不够,则触发一次Full GC。
- 新生代回收后,老年代剩余空间不足以容纳存活的对象,则触发一次 Full GC。
老年代GC ,一般 老年代GC = Full GC , 是否这样??
CMS 老年代清理器,默认情况下,老年代内存占用达到92%,就会触发清理。 触发这个清理的时候,究竟是 Full GC 还是只单单回收的老年代。??需要真实去做实验去证明。
还有个问题, Full GC 是什么? 有没有老年代 GC Old GC ??
为什么 Full GC 要比 Minor GC 慢那么多?
一般来说,Full GC 会比年轻代的 Young GC 慢十倍以上。主要原因是
- 年轻代的存活对象比较少,标记的对象也少;老年代的存活对象多。
- 垃圾清理的时候,年轻代是转移存活对象后,进行一大片连续内存清理;而老年代则是根据追踪的垃圾进行碎片化的内存清理。有疑问,但是这个清理过程是和用户进程并发进行的,应该不影响。
- 清理后,老年代还需要进行碎片整理。这个过程也比较慢。
- 清理的过程中,如果触发了 Concurrent Model Failure 的话,就会更慢了。