前言
周六日出去玩啦!围炉煮茶,超哇塞~好开心!!!就是最近冷冷的,风好大,要多穿点衣服,不能着凉感冒呀!
正文
接着上一篇,应该开始巨型分区了。
巨型分区
对 G1 垃圾收集器来说,收集是以一个分区为单位的。因此堆分区尺寸(-XX:G1HeapRegionSize)是一个非常重要的参数,因为它决定了什么尺寸的对象可以放进一个分区,堆分区尺寸同样也决定了哪些对象能被称为巨型对象。
巨型对象就是那些尺寸非常大的对象,它们至少占用一个分区的 50% 甚至更多的空间,这样的一个对象不会使用通常用到的快速分配方式,它在老年代的巨型分区里直接分配。
看图:巨型对象 1 跨越 2 个连续分区,第 1 分区被标记为开始巨型(StartsHumongous) 相邻的连续分区被标记为连续巨型。同样巨型对象 2 跨越了 3 个连续的堆分区,而巨型对象 3 只用了 1 个分区。
这里要强调一下,因为可能会引起混淆。
比如说当前G1分区大小是2MB,同时还有一个长度为1MB的字节数组。
那么这个字节数组依然会被认为是巨型对象,因为1MB的数组长度不包含数组的对象头尺寸。
注意:
对于这些巨型对象的介绍已经表明这类对象的分配比较少见,而且这些对象本身也是长期存活的。另外需要注意,巨型分区必须是连续的。同时,移动这些对象是完全没有意义的,因为没有任何益处(没有空间回收)而且大内存拷贝的开销又非常昂贵。
所以为了避免在年轻代垃圾收集的过程中拷贝这些巨型对象所带来的损失,认为在老年代中直接分配巨型对象会更好。不过近些年很多事务型应用中的巨型对象也不会存活太长时间,所以可以再努力尝试一下,使用各种措施来优化巨型对象的分配和回收。
巨型对象采用的是一种独立且相当复杂的分配路径,这也意味着巨型对象的分配方式无法利用年轻代里的 TLAB 和 PLAB 。确定新分配对象开始位置的成本会非常巨大,而且有可能不会享受到任何分配路径的优化收益。另一个值得注意的不同之处就是收集巨型分区时对巨型对象的处理手段。在JDK 8u40之前,当任何巨型分区被完全释放后,它只能在并发收集周期的清除阶段被回收。
为了优化对 “短命” 巨型对象的回收,JDK 8u40做了一个重要的修改,一旦没有任何外部引用指向巨型分区的对象,那巨型分区就可以在年轻代收集中被回收,并且释放到空闲分区列中。一次 full GC 也可以回收完全释放的巨型分区。
混合收集
随着越来越多的对象被晋升到老年代,或巨型对象被分配到巨型分区中,老年代和 Java 堆空间的占用也越来越多,为避免耗尽堆的空间,JVM需要启动一个混合的垃圾收集,它在覆盖年轻代分区的同时还覆盖了一部分老年代分区。
为了识别出垃圾最多的老年代分区,G1 GC 发起一个并发标记周期,在这过程中,GC 将对根进行标记,识别出所有的存活对象,同时计算每个分区的活跃度。这就需要在对象分配、晋升以及标记周期触发的比率上取得一个微妙的平衡,使JVM 进程不会耗尽Java 堆空间。因此,在JVM 进程启动的时候就会设置一个占用值。
一直到JDK8u45之前,这个占用阀值都不是自适应的,需要通过IHOP来设置。
命令行选项 -XX: InitiatingHeapOccupancyPercent
在 G1中,IHOP 缺省國值是 Java 堆总数的 45%。有一点需注意,这里的堆占用百分比是相对于整个 Java 堆尺寸而言的,这跟 CMS GC 中设置堆占用阙值命令行选项的含义是不同的,CMS GC 仅仅是针对老年代空间的占比。
G1 里没有物理上分割一块内存空间出来作为老年代,而是设置一个拥有很多空闲分区的内存池,这些分区可以分配为 eden、survivor、老年代或者巨型分区。同时,这些被分配的分区的数量也会随着时间而变化。所以,获取针对老年代本身的占用百分比并没有实际意义。
当老年代占用比例达到甚至超过了 IHOP 值,并发标记周期就会被触发。在标记结束的时候,G1 垃圾收集器会计算每个老年代分区的存活对象个数。同样,在清除阶段 G1 垃圾收集器会根据老年代分区的 “GC效率” 定出它们的等级。这时,混合垃圾收集就可以开始了!在一次混合收集中,G1 垃圾收集器不光会收集所有年轻代的分区,同时还会收集一部分老年代分区,这样那些垃圾最多的分区就会被回收掉。
注意:
在比较 CMS 和 G1 的 log 时我们要记住一点,就是 G1 的多级并发周期会比 CMS 的多级并发周期少几个阶段。单次的混合收集与年轻代收集是类似的,同时用拷贝的方式对存活对象进行压缩。
唯一的区别就是在混合收集过程中会包含一部分高效率老年代分区。通过一些参数,我们可以有不止一个混合收集,这被称为“混合收集周期”。混合收集周期只能在达到标记阈值 (IHOP) 并完成并发标记周期之后才能启动。
-XX:G1MixedGCCountTarget
-XX:G1HeapWastePercent
这两个重要的参数用于决定在一个混合收集周期中包含混合收集的总数。
-XX:G1MixedGccountTarget
在 JDK 8u45 中缺省值为 8,它是混合 GC 数量的目标选项,它的意义是给标记周期结束之后所能启动混合收集的数目设置一个物理限制。G1 垃圾收集器根据混合 GC 数量目标值,对可被收集的候选老年代分区总数进行平均拆分,并将结果设置为每次混合收集所要回收老年代分区的最小数量。这可以用下面的公式来描述:
每次混合收集的老年代CSet最小数量=混合收集周期将回收的候选老年代分区总数/GlMixedGCCountTarget -XX:G1HeapwastePercent,在JDK8u45 中缺省为Java堆总大小的5%,这个参数对于控制在一次混合收集周期中回收的老年代分区数有很重要的作用。
对于每次混合收集暂停,G1垃圾收集器根据那些能被回收的死亡对象的空间算出可被回收堆空间的大小。一旦 G1 垃圾收集器达到堆废物阈值百分比,G1 垃圾收集器就不会再启动新的混合收集,同时混合收集周期也将结束。
设置堆废物百分比本质上是你愿意浪费一定数量的堆空间,通过它们可以有效提升混合收集周期的效率。因此每个混合收集周期中所包含的混合收集数可以通过 2 个方面来控制:一个是每次混合收集暂停的老年代 CSet 最小数量;另一个是堆废物百分比。