当前位置: 首页 > 科技观察

5张图带你彻底了解G1垃圾收集器

时间:2023-03-19 14:53:53 科技观察

本文转载自微信公众号《程序员jinjunzhu》,作者jinjunzhu。转载本文请联系程序员jinjunzhu公众号。G1作为高效的垃圾收集器,在JDK7中加入JVM,在JDK9中取代CMS成为默认的垃圾收集器。1垃圾收集器综述1.1新生代新生代采用复制算法。有三种主要的垃圾收集器,Serial、ParallelNew和ParallelScavenge。特点如下:Serial:单线程收集器,以串行方式运行,GC进行时,其他Thread将停止工作。该集合在单核CPU上效率最高。ParallelNew:Serial的多线程版本,新生代的默认收集器。多核CPU下效率更高,可与CMS收集器配合使用。ParallelScavenge:多线程收集器,更注重吞吐量,适用于交互较少的任务。它不能与CMS一起使用。1.2SerialOld:使用mark-sort(压缩)算法,单线程采集。ParallelOld:使用mark-sorting(压缩)算法,可以和ParallelScavengeCMS结合使用:ConcurrentMarkSweep,使用mark-clear算法,采集线程可以和用户线程协同工作。CMS的缺点:吞吐量低,无法处理浮动垃圾,mark-and-sweep算法会产生大量内存碎片,并发模式失败后会切换到Serialold。G1:将堆分成多个大小相等的区域。新一代和老一代在物理上不再分离。在多核CPU和大内存的场景下有很好的表现。新生代采用复制算法,老年代采用标记-压缩(整理)算法。2G1简介2.1认识G1G1垃圾收集器主要用于多处理器和大内存场景。它有五个属性:分代的、增量的、并行的(大多数情况下是并发的)、停词和标记排序。分代:与其他垃圾收集器一样,G1将堆分为新生代和老年代。垃圾回收主要在年轻代,年轻代的回收效率最高。偶尔也会在老年代进行收集。增量式:为了缩短垃圾回收时的STW时间,G1采用增量式、逐级回收的方式。G1通过分析应用程序之前的行为和停顿时间来构建一个可预测的停顿时间模型,并利用这些信息来预测停顿期间的垃圾收集情况。例如:G1会先回收那些回收效率高的内存区域(这些空间大部分都是可回收的垃圾,这也是它被称为G1的原因)。并行和并发:为了提高吞吐量,一些操作需要STW。一些需要大量时间的操作,比如整个堆操作(比如全局标记)可以并发执行,并且可以与应用程序并行并发执行。标记:G1主要使用标记算法进行垃圾回收,将幸存的对象复制到一个新的区域,然后进行压缩,让之前的区域可以为新的对象重新分配空间。如下图所示:我们知道垃圾收集器的目标之一就是让STW(停词)尽可能的短。使用可预测的停顿时间模型,G1设置一个垃圾收集的STW目标时间(通过-XX:MaxGCPauseMillis参数设置,默认为200ms),G1尽可能在这个时间内完成垃圾收集,无需额外配置实现高吞吐量.G1致力于在以下应用和环境中寻找延迟和吞吐量的最佳平衡:堆大小达到10GB以上,超过一半的空间被存活对象占用随着系统长时间运行,速率对象分配和升级的变化很快堆中有很多内存碎片。垃圾回收时的停顿时间不要超过几百毫秒,避免垃圾回收造成的长时间停顿。如果在JDK8中使用了G1,我们可以使用参数-XX:+UseG1GC来开启。G1不是实时收集器。它会尽量以高性能完成MaxGCPauseMillis设置的暂停时间,但不能绝对保证在这个时间内完成收集。2.2堆布局G1把整个堆分成大小相等的区域。每个region是一个连续的虚拟内存,region是内存分配和回收的基本单位。如下图所示:带“S”的红色区域代表新生代幸存者,不带“S”的红色区域代表新生代伊甸园,不带“H”的浅蓝色区域代表老年代,带“H”的浅蓝色区域代表老年代的大对象。与之前G1的内存分配策略不同,survivor、eden、oldage区域可能是不连续的。当G1处于停顿状态时,它可以回收整个新生代区域,新生代区域中的对象要么复制到幸存者区域,要么复制到老年代区域。同时,每次暂停都可以回收老年代的部分内存,将老年代从一个区域复制到另一个区域。2.3关于区域在上一节中,我们看到整个堆内存被G1划分为多个大小相等的区域。每个堆可以有大约2048个区域,每个区域的大小为1~32MB(必须是2的幂)。region的大小由-XX:G1HeapRegionSize设置,所以根据默认值,G1最大可以管理的内存大约是32MB*2048=64G。2.4大对象大对象是指大小超过区域一半的对象。大对象可以跨越多个区域。大对象分配内存时,会直接分配在老年代,不会分配在eden区。如下图所示,一个大物体占据了两个半区域。为大对象分配内存时,必须从一个区域开始分配连续的区域。在大对象被回收之前,最后一个区域不能分配给其他对象。什么时候回收大型对象?通常,只有在标记结束后的Cleanup暂停阶段或FullGC期间,才会回收死大对象。但是对于基本类型的数组大对象(比如bool数组、全整型数组、float数组等)有一个例外,G1会在任何GC停顿期间回收这些死掉的大对象。这是默认启用的,但可以使用-XX:G1EagerReclaimHumongousObjects参数禁用。在分配大对象时,GC暂停可能会过早发生,因为它们占用了太多空间。G1在每次分配大对象时都会检查当前堆内存占用是否超过初始堆占用阈值IHOP(TheInitiatingHeapOccupancyPercent)。如果当前堆占用超过IHOP阈值,则立即触发初始标记。有关初始标记的详细信息,请参阅第4节。即使在FullGC期间,大对象也永远不会移动。这可能会导致过早的FullGC或意外的OOM,因为此时虽然还有很多空闲内存,但这些内存是区域中的内存碎片。3内存分配虽然G1将堆内存划分为多个区域,但是新生代和老年代的概念依然存在。G1增加了两个参数来控制新生代的内存大小,-XX:G1NewSizePercent(默认等于5),-XX:G1MaxNewSizePercent(默认等于60)。也就是说新生代的大小默认占整个堆内存的5%到60%。根据前面的介绍,一个heap大约可以分配2048个region,每个region最大可以分配到32M。这样G1管理的整个堆的大小可以达到64G,新生代占用的大小范围为3.2G~38.4G。对于-XX:G1NewSizePercent和-XX:G1MaxNewSizePercent,需要注意以下问题:如果设置了-Xmn,这两个参数是否有效?有效,比如堆大小为64G,设置了-Xmn3.2G,那么就相当于用-XX:G1NewSizePercent=5和-XX:G1MaxNewSizePercent=5,因为3.2G/64G=5%。如果设置了-XX:NewRatio,这两个参数是否有效?有效,比如堆大小为64G,设置-XX:NewRatio=3,则相当于-XX:G1NewSizePercent=25和-XX:G1MaxNewSizePercent=25。因为年轻代:老年代=1:3,也就是说年轻代占1/4=25%。如果只设置-XX:G1NewSizePercent和-XX:G1MaxNewSizePercent中的一个,这两个参数还会生效吗?设置的参数不生效,两个参数都使用默认值。如果-XX:G1NewSizePercent和-XX:G1MaxNewSizePercent这两个参数都生效,那什么时候动态扩容呢?与参数-XX:GCTimeRatio有关。该参数是0到100之间的整数(G1默认9,其他采集器默认99)。如果值为n,系统将花费不超过1/(1+n)的时间进行垃圾回收。所以G1默认最多有10%的时间进行垃圾回收,如果垃圾回收时间超过10%就会触发扩容。如果扩容失败,则发起FullGC。4垃圾回收G1的垃圾回收分两个阶段交替进行:Young-Only和Space-Reclamation。如下图所示:在young-only阶段,会使用对象逐渐填满老年代区域。在空间回收阶段,除了回收新生代的内存外,老年代的内存也会逐步回收。完成后重新启动young-only阶段。4.1Young-onlyYoung-only阶段流程如下:这个阶段从普通的young-onlyGC开始,young-onlyGC将一些对象移动到老年代。当老年代的空间占用达到IHOP时,G1停止普通的young-onlyGC,开始初始标记(InitialMark)。初始标记:除了普通的young-onlyGC之外,这个过程还会启动concurrentmarking过程,它决定了在老年代被标记的存活对象会在下一个空间回收阶段被保留。这个过程不会是STW,有可能正常的young-onlyGC会在标记结束前开始。这个标记过程需要经过重新标记(Remark)和清理(Cleanup)两个过程才能完成。Relabeling:这个过程会STW,这个过程做全局引用和类卸载。在remark和cleanup阶段之间,G1并发计算对象生存信息,这些信息用于在cleanup阶段更新内部数据结构。清理阶段:该节点回收所有空闲区域,并决定接下来是否进行空间回收。如果是,则只执行一次young-onlyGC,young-only阶段结束。对于IHOP,默认情况下,G1会在标记周期内观察标记花费了多少时间以及老年代分配了多少内存,从而自动确定一个最优的IHOP,称为自适应IHOP。如果开启该功能,由于没有足够的观测数据来确定初始IHOP,G1会使用参数-XX:InitiatingHeapOccupancyPercent来指定初始IHOP。可以使用-XX:-G1UseAdaptiveIHOP参数关闭自适应IHOP,使IHOP为参数-XX:InitiatingHeapOccupancyPercent指定的固定值。AdaptiveIHOP就是这样设置老年入住率的。当老年代占用率=老年代最大占用率-参数-XX:G1HeapReservePercent的值时,开始进行空间回收阶段的第一次MixedGC。这里参数-XX:G1HeapReservePercent用作附加缓存值。关于标记,标记使用SATB算法。在初始标记开始的时候,G1保存了一个堆的虚拟镜像,在后续的标记过程中,这个镜像存活下来的对象也被认为是存活的。这有一个问题,如果某些对象在标记过程中死亡,它们在空间回收阶段仍然存在(少数例外)。与其他垃圾收集器相比,这会导致一些死对象被错误地保留,但是它为标记阶段提供了更好的吞吐量,这些错误保留的对象将在下一个标记阶段被回收。在young-only阶段,回收新生代的区域。每次young-only结束时,G1总是调整年轻代的大小。G1可以使用参数-XX:MaxGCPauseTimeMillis和-XX:PauseTimeIntervalMillis来设置目标停顿时间,这两个参数是通过长期观察实际停顿时间得出的。他会根据GC时复制多少个对象,对象之间的关系等,计算回收同样大小的新生代内存需要多少时间。如果没有其他限制,G1会保存youngarea大小调整为-XX:G1NewSizePercent和-XX:G1MaxNewSizePercent之间的值,以满足停顿时间要求。4.2Space-reclamation该阶段由多个MixedGC组成,既回收新生代垃圾,也回收老年代垃圾。当G1发现回收更多的老年代区域不能释放更多的空闲空间时,这个阶段结束。之后,定期再次开始一个新的Young-only阶段。当G1在收集存活对象信息时内存不足时,G1会做一次FullGC和STW。在空间回收阶段,G1会在GC暂停期间尝试回收尽可能多的老年代内存。在这个阶段,新生代的内存大小被调整为-XX:G1NewSizePercent设置的最小允许值。只要有可回收的老年代区域,就会被添加到回收集合中,直到添加超过目标暂停时间。在一定的GC停顿时间内,G1会根据老年代region回收效率(高效优先collection)和剩余可用时间,获取最终要回收region的collection。每次GC停顿期间要回收的老年代区域数量受候选区域集数量除以参数值-XX:G1MixedGCCountTarget的限制。参数-XX:G1MixedGCCountTarget指定一个周期内最大触发MixedGC的次数,默认值为8。例如-XX:G1MixedGCCountTarget采用默认值8,候选region集合有200个region,所以在每次暂停期间收集25个区域。候选region集合为老年代中所有占用率低于-XX:G1MixedGCLiveThresholdPercent的region。当设置回收区域的可回收空间占用率低于参数值-XX:G1HeapWastePercent时,Space-Reclamation结束。4.3内存紧张当应用程序存活的对象占用大量内存,以至于没有足够的空间来复制剩余的对象时,就会触发evacuation失败。这时,为了完成本次垃圾回收,G1会保持新位置存活的对象不变,不会复制没有移动或复制的对象,只调整对象间的引用。疏散失败会导致一些开销,但通常与其他年轻GC一样快。疏散失败完成后,G1将继续恢复正常执行应用程序。G1假定疏散失败发生在GC后期,此时大多数对象已被移动并且有足够的内存继续执行应用程序直到标记结束和空间回收开始。如果这个假设不成立(即没有足够的内存来执行应用程序),G1最终只能发起FullGC,对整个堆进行压缩。这个过程可能非常缓慢。5与其他收集器的比较5.1ParallelGCParallelGC可以对老年代的内存进行压缩回收,但是只能对老年代整体进行操作。G1以牺牲吞吐量为代价,将整个GC工作增量地分布在多个更短的暂停时间内。5.2CMS类似于CMS。G1并发回收老年代的内存。但是CMS采用mark-clear算法,不会对老年代的内存碎片进行处理,最终会导致长期的FullGC。5.3G1问题因为使用并发收集,G1的性能开销会更大,可能会影响吞吐量。5.4G1的优点G1可以在任何GC期间回收老年代中完全空的或占用较大空间的内存。这避免了一些不必要的GC,因为可以很容易地释放大量内存。默认情况下启用此功能,可以使用-XX:-G1EagerReclaimHumongousObjects参数禁用。G1可以选择并行地对整个堆中的String进行去重。该功能默认是禁用的,可以通过参数-XX:+G1EnableStringDeduplication启用。6小结本文详细介绍了G1垃圾收集器,希望对大家了解G1有所帮助。参考:https://docs.oracle.com/javase/10/gctuning/garbage-first-garbage-collector.htm#JSGCT-GUID-CE6F94B6-71AF-45D5-829E-DEADD9BA929Dhttps://mp.weixin.qq。com/s/KkA3c2_AX6feYPJRhnPOyQ