• 第八篇章——垃圾回收器


    垃圾回收器

    本专栏学习内容来自尚硅谷宋红康老师的视频以及《深入理解JVM虚拟机》第三版

    有兴趣的小伙伴可以点击视频地址观看,也可以点击下载电子书

    有了虚拟机,就一定需要收集垃圾的机制,这就是Garbage Collection(GC),对应的产品(垃圾收集器)称为Garbage Collector(GC)两个都称为GC,本文中的GC理解为垃圾收集器

    分类

    • 垃圾收集器没有在规范中进行过多的规定,可以由不同厂商、不同版本的JVM来实现
    • 由于JDK的版本处于高速迭代过程中,因此Java发展至今已经衍生了众多的GC版本
    • 从不同角度分析垃圾收集器,可以将GC分为不同的类型

    按线程数分

    • 串行垃圾回收器
      • 串行回收指的是同一时间段内只允许有一个CPU用于执行垃圾回收操作,此时工作线程被暂停,直至垃圾收集工作结束
    • 并行垃圾回收器
      • 与串行相反,并行收集可以运用多个CPU同时执行垃圾回收,因此提升了应用的吞吐量,不过并行回收仍然和串行回收一样,采用独占式,使用STW机制

    按工作模式分

    • 并发式垃圾回收器
      • 并发式垃圾回收器与应用线程交替工作,尽可能减少应用程序的停顿时间
    • 独占式垃圾回收器
      • 独占式垃圾回收器一旦运行,就停止应用程序中的所有用户线程,知道垃圾回收过程完全结束

    按碎片处理方式分

    • 压缩式垃圾回收器
      • 压缩式垃圾回收器会在回收完成后,对存活对象进行压缩整理,消除回收后的碎片
    • 非压缩式垃圾回收器
      • 非压缩式垃圾回收器不会进行这步操作

    按工作的内存区间分

    • 年轻代垃圾回收器
    • 老年代垃圾回收器

    评估GC的性能标准

    吞吐量、暂停时间、内存占用三者不可能构成一个三角形,三者的表现会随着技术的进步而越来越好,一款优秀的收集器通常最多同时满足其中两项,主要抓住两点:吞吐量,暂停时间

    • 吞吐量:运行用户代码的时间占总运行时间的比例,该比例越高说明吞吐量越大
      • 总运行时间=程序运行时间+内存回收时间
    • 垃圾收集开销:吞吐量的补数,内存回收时间与总运行时间的比例
    • 暂停时间:执行垃圾收集时,程序的工作线程被暂停的时间
    • 收集频率:相对于应用程序的执行,收集操作发生的频率
    • 内存占用:Java堆所占用的内存大小
    • 快速:一个对象从诞生倒被回收所经历的时间

    吞吐量

    • 吞吐量就是CPU用于运行用户代码的时间与CPU总消耗时间的比值,即吞吐量 = 运行用于代码时间 / (运行用户代码时间 + 垃圾手机时间)
      • 例如虚拟机总共运行了100分钟,其中垃圾收集花掉了1分钟,那么吞吐量就是99%
    • 这种情况下,应用程序能容忍较高的暂停时间,因此,高吞吐量的应用程序有更长的时间基准,快速响应是不必考虑的
    • 吞吐量优先,意味着在单位时间内,STW的时间最短

    吞吐量与暂停时间对比

    如下图所示,在注重吞吐量的情况下,每次GC的暂停时间会较长,在注重低延迟的情况下,吞吐量会较低。

    所以,在设计GC算法时,必须确定我们的目标,一个GC算法只可能针对两个目标之一(专注于较大吞吐量或最小暂停时间),或尝试找到一个二者的折中

    现在的标准是:在最大吞吐量优先的情况下,降低停顿时间。

    在这里插入图片描述

    垃圾回收器的组合

    如下图所示,7款经典的垃圾收集器往往是在不同代之间工作的。

    • 新生代收集器:Serial、ParNew、Parallel Scavenge
    • 老年代收集器:Serial Old、Parallel Old、CMS
    • 整堆收集器:G1

    在这里插入图片描述

    在执行GC的过程中,往往都是多个垃圾收集器组合使用。

    两个收集器之间有连线,表面他们可以搭配使用。

    在这里插入图片描述

    查看默认的垃圾收集器

    方式一:通过-XX:+PrintCommandLineFlags查看命令行相关参数(包括默认的垃圾收集器)

    这里看到JDK8中在新生代中默认使用Parallel GC,那么老年代则是使用Parallel Old GC

    在这里插入图片描述

    方式二:通过命令行指令 jinfo -flag 相关垃圾回收器参数 进程ID

    如下图所示,可以看到JDK8默认使用的时Parallel组合,而且并没有使用G1垃圾收集器

    在这里插入图片描述

    Serial垃圾回收器:串行回收

    概念

    • Serial收集器是最基本、历史最悠久的垃圾收集器了。是JDK1.3以前回收新生代的唯一选择
    • Serial收集器作为HotSpot中Client模式下的默认行省代垃圾收集器
    • Serial采用复制算法、串行回收和STW机制的方式执行内存回收
    • 除了年轻代之外,Serial收集器还提供用于执行老年代垃圾收集的Serial Old收集器,Serial Old收集器同样也采用了串行回收和STW机制,只不过内存回收算法使用的是标记-压缩算法
      • Serial Old是运行在Client模式下默认的老年代的垃圾回收器
      • Serial Old在Server模式下主要有两个用途
        1. 与新生代的Parallel Scavenge配合使用
        2. 作为老年代CMS收集器的后备垃圾收集方案(之后会讲到)

    图解

    如下图所示,这个收集器是一个单线程的收集器,但它的单线程的意义并不仅仅说明它只会使用一个CPU或一条收集线程去完成垃圾回收工作,更重要的是它在进行垃圾收集时,必须暂停其他所有的工作线程,直到它收集结束。

    在这里插入图片描述

    使用场景

    • 优势:简单而高效(与其他收集器的单线程相比),对于限定单个CPU的环境来说,Serial收集器由于没有线程交互的开销,专心做垃圾收集自然可以获得最高的单线程收集效率
    • 在用户的桌面应用场景中,可用内存一般不大,可以在较短时间内完成垃圾收集,只要不频繁发生,使用串行回收器是可以接受的
    • 在HotSpot虚拟机中,使用-XX:UseSerialGC参数可以指定年轻代和老年代都适用串行收集器
      • 等价于新生代用Serial GC,且老年代用Serial Old GC

    ParNew垃圾回收器:并行回收

    概念

    • 如果说Serial GC是年轻代中的单线程垃圾收集器,那么ParNew收集器则是Serial收集器的多线程版本
      • Par是Parallel的缩写,New:只能处理的是新生代
    • ParNew收集器除了采用并行回收的方式执行内存回收外,两款垃圾收集器之间几乎没有任何区别,ParNew收集器在年轻代中同样也是采用复制算法,STW机制
    • ParNew是很多JVM在Server模式下新生代的默认垃圾回收器

    图解

    如下图所示,对于新生代,回收次数频繁,使用并行方式高效;对于老年代,回收次数少,使用串行方式节省资源(CPU并行需要切换线程,串行可以省去切换线程的资源)

    在这里插入图片描述

    使用场景

    • 由于ParNew收集器基于并行回收,那么是否可以断定ParNew收集器的回收效率在任何场景下都会比Serial收集器更高效?
      • ParNew收集器运行在多CPU的环境下,由于可以充分利用多CPU、多核心等物理硬件资源优势,可以更快速的完成垃圾收集,提升程序的吞吐量
      • 在单个CPU的环境下,ParNew收集器不比Serial收集器更高效。虽然Serial收集器基于串行回收,但是由于CPU不需要频繁的做任务切换,因此可以有效避免多线程交互过程中产生的一些额外开销
    • 除了Serial外,目前只有ParNew GC可以与CMS收集器配合工作
    • 在程序中,开发人员可以通过-XX:+UseParNewGC手动指定使用ParNew收集器执行内存回收任务,他表示年轻代使用并行收集器,不影响老年代
    • 另外,-XX:ParallelGCThreads可以限制线程数量,默认开启和CPU数据相同的线程数

    Parallel Scavenge回收器:吞吐量优先

    概念

    • HotSpot的年轻代除了拥有ParNew收集器是基于并行回收的以外,Parallel Scavenge收集器同样也采用复制算法、并行回收和STW机制
    • 和ParNew收集器不同,Parallel Scavenge收集器的目标则是达到一个可控制的吞吐量,它也被称为吞吐量优先的垃圾收集器
    • 自适应调节策略也是Parallel Scavenge与ParNew一个重要区别
    • 高吞吐量则是可以高效率的利用CPU时间,尽快完成程序的运算任务,主要适合在后台运算而不需要太多交互的任务,因此,常见在服务器环境中使用。例如:执行批量处理、订单处理、工资支付、科学计算的应用程序
    • Parallel Scavenge收集器在JDK1.6时提供了用于执行老年代垃圾收集的Parallel Old收集器,用来代替Serial Old收集器
    • Parallel Old收集器采用了标记-压缩算法,同样也是并行回收和STW机制

    在这里插入图片描述

    参数设置

    • -XX:+UseParallelGC:手动指定年轻代使用Parallel并行收集器执行内存回收任务
    • -XX:+UseParallelOldGC:手动指定老年代使用并行回收器
      • 分别适用于年轻代和老年代,默认JDK8是开启的
      • 上面两个参数,默认开启一个,另一个也会被开启
    • -XX:ParallelGCThreads:设置年轻代并行收集器的线程数,一般的,最好与CPU数量相同,以避免过多的线程数影响垃圾收集性能
      • 在默认情况下,当CPU数量小于8个,ParallelGCThreads值等于CPU数量
      • 当CPU数量大于8个,ParallelGCThreads值等于3+5*CPU_Count/8
    • -XX:MaxGCPauseMillis:设置垃圾收集器最大停顿时间(即STW时间,单位毫秒)
      • 为了尽可能把停顿时间控制在MaxGCPauseMillis以内,收集器在工作时会调整Java堆大小或者其他一些参数
      • 对于用户来讲,停顿时间越短体验越好,但是在服务器端,我们注重高并发,整体的吞吐量。所以服务器端适合Parallel进行控制
      • 该参数使用需谨慎
    • -XX:GCTimeRedio:垃圾收集时间占总时间的比例( = 1 / (N + 1))
      • 取值范围(0,100)。默认值99,也就是垃圾回收时间不超过程序运行时间的1%
      • 与前一个-XX:MaxGCPauseMillis参数有一定矛盾性,暂停时间越长,Redio参数就容易超过设定的比例
    • -XX:+UseAdaptiveSizePolicy:设置Parallel收集器具有自适应调节策略
      • 在这种模式下,年轻代的大小、Eden区和Survivor的比例、晋升老年代的对象年龄等参数都会被自动调节,以达到在堆大小、吞吐量和停顿时间之间的平衡点
      • 在手动调优比较困难的场合,可以直接使用这种自适应的方式,仅指定虚拟机的最大堆、目标的吞吐量和停顿时间,让虚拟机自己完成调优工作

    CMS回收器:低延迟

    概念

    • CMS收集器是HotsSpot虚拟机中第一款真正意义上的并发收集器,它第一次实现了让垃圾收集线程与用户线程同时工作
    • CMS收集器的关注点是尽可能缩短垃圾收集时用户线程的停顿时间。停顿时间越短就越适合与用户交互的程序,良好的响应速度能提升用户体验
    • CMS的垃圾收集算法采用标记-清除算法,并且也会STW

    图解

    CMS整个过程分为4个主要阶段

    • 初始标记阶段:在这个阶段,程序中所有的工作线程都将会因为STW机制而穿线短暂的暂停,这个阶段的主要任务仅仅只是标记处GC Roots能直接关联到的对象。一旦标记完成之后就会恢复之前被暂停的所有应用线程。由于直接关联的对象比较少,所以这里速度非常快。
    • 并发标记阶段:从GC Roots的直接关联对象开始遍历整个对象图的过程,这个过程耗时较长但是不需要停顿用户线程,可以与垃圾收集线程一起并发运行。
    • 重新标记阶段:由于在并发标记阶段中,程序的工作线程会和垃圾收集线程同时运行或者交叉运行,因此为了修正并发标记期间,因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间通常会比初始标记阶段稍长一些,但也远比并发标记阶段的时间短。
    • 并发清除阶段:此阶段清理删除掉标记阶段判断的已经死亡的对象,释放内存空间。由于不需要移动存活对象,所以这个阶段也是可以与用户线程同时并发的。

    在这里插入图片描述

    特点

    由于最耗费时间的并发标记与并发清除阶段都不需要暂停工作,所以整体的回收是低停顿的。

    因为在垃圾收集阶段用户线程没有中断,所以在CMS回收过程中,还应确保应用程序用户线程有足够的内存可用。因此CMS收集器不能像其他收集器那样等到老年代几乎被完全填满了在进行收集,而是当堆内存使用率达到某一阈值,便开始进行回收,以确保应用程序在CMS工作中依然有足够的空间支持应用程序运行。

    要是CMS运行期间预留的内存无法满足程序需要,就会出现一次“Concurrent Mode Failure”失败,这时虚拟机将启动后备预案:临时启用Serial Old收集器来重新进行老年代的垃圾收集,这样停顿时间就很长了。

    另外,CMS采用的是标记-清除算法,这意味着每次执行完回收后,不可避免的产生一些内存碎片,那么在为新对象分配内存空间时,只能使用空闲列表执行内存分配。

    为什么不把算法替换成标记-压缩算法

    因为清理的线程与用户线程是并发的,如果要整理的话必定要将对象的引用地址改变,用户线程中的地址就无法正常使用。

    优点
    • 并发收集
    • 延迟低
    缺点
    • 会产生内存碎片,导致并发清除后,用户线程可用空间不足,在无法分配大对象的情况下,不得不提前出触发Full GC
    • CMS收集器对CPU资源非常敏感,在并发阶段,它虽然不会导致用户停顿,但是会因为占用了一部分线程而导致应用程序变慢,总吞吐量降低
    • CMS收集器无法处理浮动垃圾。在并发标记阶段时,用户线程尽可能会产生新的垃圾对象,CMS无法对这些对象进行标记,最终会导致这些对象无法被及时回收

    参数设置

    • -XX:+UseConcMarkSweepGC:手动指定使用CMS收集器执行内存回收任务

      • 开启该参数,会将-XX:+UseParNewGC打开
    • -XX:CMSInitiatingOccupanyFraction:设置堆内存使用率的阈值,一旦达到这个阈值,便开始回收

      • JDK5及以前版本默认值是68,当老年代空间使用率达到68%时,会执行一次CMS,JDK6及以上版本默认值时92
    • -XX:UseCMSCompactAtFullCollection:用于指定在执行完Full GC后堆内存空间进行压缩整理,以避免内存碎片的产生,不过由于内存压缩整理过程无法并发执行,所带来的问题就是停顿时间变长了

    • -XX:CMSFullGCsBeforeCompaction:设置在执行多少次Full GC后对内存空间进行压缩整理

    • -XX:ParallelCMSThreads:设置CMS的线程数量

      • 默认线程数是(ParallelCMSThreads + 3)/ 4
      • ParallelCMSThreads是年轻代并行收集器的线程数,当CPU资源比较紧张时,受到CMS收集器线程的影响,应用程序的性能在垃圾回收阶段可能会非常糟糕

    小结

    如何选择三组回收器?

    • 如果想要最小化的使用内存和并行开销,请选择Serial GC
    • 如果想要最大化应用程序的吞吐量,请选择Parallel GC
    • 如果想要最小化GC的中断或停顿时间,请选择CMS GC

    G1回收器:区域分代化

    概念

    在JDK9以后,G1就是默认的垃圾回收器,去带哦了CMS回收器一起Parallel + Parallel Old组合

    • G1是一个并行回收器,它把堆内存分割为很多不相关的区域(Region)(物理上不连续的)。使用不同的Region来表示Eden区、幸存者0区、幸存者1区、老年代等
    • G1有计划的避免在整个Java堆中进行全区域的垃圾收集,G1跟踪各个Region里面的垃圾堆积的价值大小,在后台维护一个优先列表,每次根据允许收集时间,优先回收价值最大的Region
    • 由于这种方式的侧重点在于回收垃圾最大量的区间,所以G1叫做垃圾有限(Garbage First)

    优势

    与其他GC收集器相比,G1使用全新的分区算法,其特点如下

    • 并行与并发

      • 并行性:G1在回收期间,可以有多个GC线程同时工作,有效利用多核计算能力,此时用户线程STW
      • 并发性:G1拥有与应用程序交替执行的能力,部分工作可以和应用程序同时执行,因此,一般来说,不会在整个回收阶段发生完全阻塞应用程序的情况
    • 分代收集

      • 从分代上看,G1依然属于分代型垃圾回收器,它会区分年轻代和老年代,年轻代依然有Eden区和Survivor区,但从堆的结构上看,他不要求整个区域都是连续的,也不再坚持固定大小和固定数量
      • 将整个堆空间分为若干个区域,这些区域包含了逻辑上的年轻代和老年代
      • 和之前回收器不同,它同时兼顾年轻代和老年代
    • 空间整合

      • CMS:采用标记清除算法,会出现内存碎片,若干次GC后需要进行一次碎片整理
      • G1将内存划分为一个个的region,内存的回收是以region作为基本单位的,region之间是复制算法,但整体上实际可看作标记压缩算法,两种算法都可以避免内存碎片,这种特性有利于程序长时间运行,分配大对象时不会因为无法找到连续内存空间而提前触发下一次GC,尤其是当Java堆空间非常大时,G1的优势更加明显
    • 可预测的停顿时间模型

      这是G1相对于CMS的另一大优势,G1除了追求低停顿之外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒。也就是说可以控制程序吞吐量

      • 由于分区的原因,G1可以只选取部分区域进行内存回收,这样缩小了回收的范围,因此对于全局停顿情况的发生也能得到较好的控制
      • G1跟踪各个Region里面的垃圾堆积的价值大小,在后台位于一个优先列表,每次根据允许的手机时间,优先回收价值最大的Region。保证了G1收集器在有限的时间内可以获取尽可能高的收集效率
      • 相比于CMS,G1未必能做到CMS在最好情况下的延时停顿,但是最差情况要好很多

    缺点

    相较于CMS,G1还不具备全方位、压倒性优势,比如在用户程序运行过程中,G1无论是为了垃圾收集产生的内存占用还是程序运行时额外执行负载都要比CMS要高。

    从经验上来说,在小内存应用上CMS的表现大概率会优于G1,而G1在大内存应用上则发挥其优势。平衡点在6-8GB之间

    参数设置

    • -XX:+UseG1GC:手动指定使用G1收集器执行内存回收任务
    • -XX:G1HeapRegionSize:设置每个Region的大小。值是2的幂次方,范围是1MB到32MB之间,目标是根据最小的Java堆大小划分出约2048个区域。默认是堆内存的1/2000
    • -XX:MaxGCPauseMillis:设置期望达到最大GC停顿时间指标(JVM会尽力实现,但不保证达到),默认值是200ms
    • -XX:ParallelGCThreads:设置STW工作线程数的值,最多设置为8
    • -XX:ComcGCThreads:设置并发标记的线程数,将n设置为并行垃圾回收线程数(ParallelGCThreads)的1/4左右
    • -XX:InitiatingHeapOccupancyPercent:设置触发并发GC周期的Java堆占用阈值,超过此值,就会触发GC,默认值是45.

    分区Region:化整为零

    在介绍G1的垃圾回收过程之前,先来了解一下Region的相关知识。

    所有的Region大小相同,且在JVM生命周期内不会被改变。

    在这里插入图片描述

    • 一个region有可能属于Eden,Survivor或者Old内哦村区域,但是region只能属于一个角色,图中的E表述region属于Eden区,S表述Survivor区,O表示old区,空表表述未使用的内存空间。
    • G1垃圾收集器还增加了一种新的内存区域,叫做Humongous内存区域,主要用于存储大对象,如果对象的大小超过了1.5个region,就放到这里

    为什么设置humongous区域?

    对于堆中的大对象,默认直接分配到老年代,但如果他是一个短期存在的大对象,就会对垃圾收集器造成负面影响,而humongous区就是专门用来存放大对象。**如果一个H区装不下一个大对象,那么G1会寻找连续的H区来存储。**为了能找到连续的H区,有时候不得不启动Full GC,G1大多数行为都把H区当作老年代的一部分看待。

    Remembered Set(记忆集)

    这是一个对象被不同区域引用的问题

    一个region不可能是孤立的,一个region中的对象可能被其他region中的对象引用,那么判断对象存货是,就需要扫描整个Java堆才能保证准确,这样会降低Minor GC的效率

    解决方案

    • 无论是G1还是其他分代收集器,JVM都是用记忆集来避免全局扫描
    • 每一个region都有一个对于的记忆集
    • 每次reference类型数据写操作时,都会产生一个Write Barrier暂时终端操作
    • 然后检查将要写入的引用对象是否和该reference类型数据在不同的region
    • 如果不同,通过Card Table把相关引用信息记录到引用指向对象所在的region对应的记忆集中
    • 当进行垃圾收集时,在GC根节点的枚举范围加入记忆集,就可以保证不进行全局扫描也不会有遗漏

    如下图所示,Region1和Region3分别引用了Region2中的两个对象,那么Region2对应的记忆集就会保存两个引用记录信息,当对Region2进行垃圾回收时,只需要遍历Region2和对应的结果集即可。

    在这里插入图片描述

    垃圾回收过程

    G1的垃圾回收过程主要包括以下三个环节

    • 年轻代GC
    • 老年代并发标记过程
    • 混合回收
    • (如果需要,单线程、独占式、高强度的Full GC还是继续存在的,它针对GC的评估失败提供了一种失败保护机制,即强力回收)

    在这里插入图片描述

    回收过程一:年轻代GC

    JVM启动时,G1先准备好Eden区,程序在运行过程中不断创建对象到Eden区,当Eden区空间耗尽时,G1会启动一次念你清代垃圾回收过程。年轻代垃圾回收只会回收Eden区和Survivor区。

    YGC时,首先G1停止应用程序的执行(STW),G1创建回收集(Collection Set),回收集是指需要被回收的内存分段的集合,年轻代回收过程的回收集包含年轻代Eden区和Survivor区所有的内存分段。然后开始如下回收过程:

    1. 扫描根

      根是指static变量指向的对象,正在执行的方法调用链条上的局部变量等。根引用连同RSet(结果集)记录的外部引用作为扫描存活对象的入口

    2. 更新RSet

      处理dirty card queue中的card,更新RSet。此阶段完成后,RSet可以准确的反映老年代对所在的内存分段中对象的引用。

      在年轻代回收时,G1对会对dirty card queue中的所有的card进行处理,以更新RSet,保证RSet实时准确的反映引用关系。

    3. 处理RSet

      识别被老年代对象指向的Eden区中的对象,这些被指向的Eden中的对象被认为是存活的对象

    4. 复制对象

      此阶段,对象树被遍历,Eden区内存段中存活的对象会被复制到Survivor区中空的内存分段,Survivor区内存段中存活的对象如果年龄未达到阈值,年龄会加1,达到阈值会被福州道Old区中空的内存分段,如果Survivor空间不够,Eden空间的部分数据会直接晋升到老年代

    5. 处理引用

      处理Soft、Weak、Phantom、Final、JNI Weak等引用。最终Eden空间的数据为空,GC停止工作,而目标内存中的对象都是连续存储的,没有碎片,所以复制过程可以达到内存整理的效果,减少碎片。

    回收过程二:并发标记过程
    1. 初始标记阶段

      标记从根节点直接可达的对象,这个阶段是STW的,并且会触发一次年轻代GC

    2. 根区域扫描

      G1扫描Survivor区直接可达的老年代区域对象,并标记被引用的对象,这一过程必须在young GC之前完成

    3. 并发标记

      在整个堆中进行并发标记(和应用程序并发执行),此过程可能被young GC中断。在并发标记阶段,若发现区域对象中的所有对象都是垃圾,那这个区域会被立即回收。同时,并发标记过程中,会计算每个区域的对象活性(区域中存活对象的比例)

    4. 再次标记

      由于应用程序持续进行,需要修正上一次的标记结果,是STW的。G1中采用比CMS更快的初始快照算法

    5. 独占清理

      计算各个区域的存活对象和GC回收比例,并进行排序,识别可以混合回收的区域,为下阶段做铺垫,是STW的(这个阶段并不会实际上去做垃圾收集)

    6. 并发清理阶段

      识别并清理完全空闲的区域

    回收过程三:混合回收

    当越来越多的对象晋升到老年代region时,为了避免堆内存被耗尽,虚拟机会触发一个混合的垃圾收集器,即Mixed GC,该算法并不是一个Old GC,除了回收整个年轻代region,还会回收一部分的老年代region。这里需要注意,是一部分老年代,而不是全部老年代。可以选择哪些老年代region进行收集,从而可以对垃圾回收的耗时时间进行控制。也要注意的是Mixed GC并不是Full GC。

    • 并发标记结束后,老年代中百分百为垃圾的内存分段被回收了,部分为垃圾的内存分段被计算了出来。默认情况下,这些老年代的内存分段会分8次被回收
    • 混合回收的回收集包括八分之一的老年代内存分段,Eden区内存分段,Survivor内存分段。混合回收的算法和年轻代回收的算法完全一样,知识回收集多了老年代的内存分段
    • 由于老年代中的内存分段默认分8次回收,G1会优先回收垃圾多的内存分段。**垃圾占内存分段比例越高的,越会被先回收。**并且有一个阈值会决定内存分段是否被回收,-XX:G1MixedGCLiveThresholdPercent,默认为65%,意思是垃圾占内存分段比例要达到65%才会被回收。如果垃圾占比太低,意味着存活的对象占比高,在复制的时候会花费更多的时间
    • 混合回收不一定要进行8次,有一个阈值-XX:G1HeapWastePercent,默认值为10%,意思是允许整个堆内存中有10%的空间被浪费,意味着如果发现可以回收的垃圾占堆内存的比例低于10%,则不进行混合回收,因为GC会花费更多的时间但是回收道德内存却很少
    可选的回收过程四:Full GC

    G1的初衷就是避免Full GC的出现。但是如果上述方式不能正常工作,G1会停止应用程序执行,使用单线程的内存回收算法进行垃圾回收,性能会非常差,应用程序停顿时间会很长。

    导致G1Full GC的原因可能有两个

    1. 回收的时候没有足够的空间来存放晋升的对象
    2. 并发处理过程完成之前空间耗尽

    G1回收器优化建议

    • 年轻代大小
      • 避免使用-Xmn-XX:NewRatio等相关选项显式设置年轻代大小
      • 固定年轻代的大小会覆盖暂停时间目标
    • 暂停时间目标不要太过苛刻
      • G1的吞吐量目标是90%的应用程序时间和10%的垃圾回收时间
      • 评估G1的吞吐量时,暂停时间目标不要太严苛,目标太过严苛表示你愿意承受更多的垃圾回收开销,而这些会直接影响到吞吐量
  • 相关阅读:
    Java版企业电子采购招标系统源码
    Linux防火墙常用操作及端口开放
    基于单片机GP2D12测距-proteus仿真-源程序
    力扣每日一题-第27天-561.数组拆分Ⅰ
    Netty系列(一):Springboot整合Netty,自定义协议实现
    内网渗透神器CobaltStrike之重定向服务配置(二)
    【Python+selenium】自动化生成测试报告
    C语言之深入指针(三)(详细教程)
    c++day2---9.7
    Python学习笔记:Jupyter Notebook快速入门案例:学习时间与成绩的关系
  • 原文地址:https://blog.csdn.net/Yellow_Star___/article/details/126605010