堆内存分配


对于堆内存,很多垃圾回收器都会按照以下的策略分配新生代的空间。
image.png

新的对象会优先在 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,进行回收后,会出现以下情况。

  1. Minor GC 后的存活对象小于 Survivor 的剩余空间,就直接进入 Survivor 区。情况比较良好。
  2. Minor GC 后的存活对象大于 Survivor 的剩余空间,老年代的可以装得下存活的对象,那么就会直接进入老年代。
  3. Minor GC 后的存活对象,Survivor 剩余空间装不下,老年代剩余空间也装不下。那么就会触发一次 Full GC,进行老年代空间回收。Full GC 后,老年代的剩余空间还是放不下存活的对象,那么就会报内存溢出错误,也就是 OOM,因为内存确实不够了。

GC 的触发时机


根据上面分析的新生代和老年代机制,得出一般情况下 GC 的触发时机。

什么时候 触发 Young GC?

新生代的分配内存差不多满了,继续申请内存创建对象,发现内存不够的时候,就会触发新生代的回收。

什么时候 触发 Full GC?

一般来说是2个时机:

  1. 新生代回收发生前,检查老年代剩余的空间是否可以容纳可能进入老年代的存活对象,如果空间不够,则触发一次Full GC。
  2. 新生代回收后,老年代剩余空间不足以容纳存活的对象,则触发一次 Full GC。

老年代GC ,一般 老年代GC = Full GC , 是否这样??
CMS 老年代清理器,默认情况下,老年代内存占用达到92%,就会触发清理。 触发这个清理的时候,究竟是 Full GC 还是只单单回收的老年代。??需要真实去做实验去证明。

还有个问题, Full GC 是什么? 有没有老年代 GC Old GC ??

为什么 Full GC 要比 Minor GC 慢那么多?


一般来说,Full GC 会比年轻代的 Young GC 慢十倍以上。主要原因是

  1. 年轻代的存活对象比较少,标记的对象也少;老年代的存活对象多。
  2. 垃圾清理的时候,年轻代是转移存活对象后,进行一大片连续内存清理;而老年代则是根据追踪的垃圾进行碎片化的内存清理。有疑问,但是这个清理过程是和用户进程并发进行的,应该不影响。
  3. 清理后,老年代还需要进行碎片整理。这个过程也比较慢。
  4. 清理的过程中,如果触发了 Concurrent Model Failure 的话,就会更慢了。