【基础篇】十五、JVM垃圾回收器

发布时间:2024年01月08日

1、垃圾回收器

对回收算法落地,出现了多种垃圾回收器:

在这里插入图片描述

搭配有:

  • G1用于年轻代和老年代
  • Serial + Serial Old
  • ParNew + CMS
  • Parallel Scavenge + Parallel Old

图中虚线为对应版本的废弃组合。

2、Serial + Serial Old

Serial:

  • 串行、单线程的年轻代垃圾回收器
  • 算法:复制算法
  • 优点:单CPU下吞吐高
  • 缺点:堆内存大时,会让用户线程长期等待,多CPU下性能差
  • 场景:单CPU,硬件配置有限

在这里插入图片描述

Serial Old:

  • Serial回收器的老年代版本
  • 依旧串行、单线程回收
  • 算法:标记整理算法

在这里插入图片描述

添加JVM参数:

-XX:+UseSerialGC 

新生代、老年代即使用Serial + Serial Old回收器组合。

3、ParNew回收器

  • 实质是对Serial在多CPU下的优化,作用于年轻代
  • 使用多线程进行垃圾回收
  • 算法:复制
  • 优点:多CPU下STW时间短
  • 缺点:回收过程相对粗暴,吞吐和STW都不如G1
  • 场景:搭配老年代的CMS

在这里插入图片描述
添加JVM参数:

-XX:+UseParNewGC

此参数对应的组合是:ParNew + Serial Old(已过时的组合)

在这里插入图片描述

4、CMS回收器

  • Concurrent Mark Sweep
  • 老年代的回收器
  • 算法:标记清除算法
  • 优点:STW时间短
  • 场景:用户请求数据量大、频率高的场景,比如订单接口、商品接口等

CMS步骤:

  • 初始标记:标记和GC Root 直接关联 的对象(不是遍历所有的引用链,因此耗时极短),此过程STW
  • 并发标记:并发标记所有对象,此过程不用暂停用户线程(不用STW)
  • 重新标记:上面并发时,有些对象的状态发生了变化,可能漏标或者错标(死了的又活了,活着的快死了),需要重标,此过程STW(但大部分对象上一步并发标记已经处理完了,漏标的占少数,所以这步STW时间短)
  • 并发清理:清理死亡对象,用户线程不用暂停(不用STW)

整个过程,有两个步骤需要STW,而这两步又耗时最短,因此,CMS的SWT短
在这里插入图片描述

CMS的缺点:

  • 内存碎片化问题:标记清除算法下产生的,CMS垃圾回收器会在Full GC后进行碎片整理,此过程STW
//调整多少次Full GC后进行整理
-XX:CMSFullGCsBeforeCompaction=N 参数(默认0
  • 浮动垃圾问题:最后一步并发清理的时候,用户线程可能又产生了垃圾,这些只能等下波了
  • 退化问题:老年代内存不足时,会退化为Serial Old
  • 线程资源争抢问题:一定的CPU核数下,并发时,CMS标记或清理线程多了,其余执行用户代码的线程就少了(CMS并发线程数可设置)

在这里插入图片描述
在这里插入图片描述

基于以上缺点,JDK14后废弃CMS。添加JVM参数:

-XX:+UseParNewGC -XX:+UseConcMarkSweepGC

新老两块的回收器搭配就是ParNew + CMS

5、Parallel Scavenge + Parallel Old

Parallel Scavenge:

  • JDK8默认的年轻代垃圾回收器
  • 多线程并行回收,关注吞吐量(用户代码执行时间/用户时间+GC时间)
  • 自动调整堆的大小
  • 算法:复制算法
  • 优点:吞吐量高且开发者可控吞吐(JVM动态调整堆的大小来满足设置的吞吐)
  • 缺点:不保证单次停顿时间(STW不保证)
  • 场景:用于大数据的处理、大文件的导出(和用户没交互,容易产生大量对象)

在这里插入图片描述

Parallel Old:

  • Parallel Scavenge的老年代版本
  • 依旧多线程
  • 算法:标记清除、整理

在这里插入图片描述

添加如下JVM参数,可以使用Parallel Scavenge + Parallel Old组合:

-XX:+UseParallelGC-XX:+UseParallelOldGC

最大暂停时间参数:

//毫秒
-XX:MaxGCPauseMillis=n

吞吐的设置:

-XX:GCTimeRatio=n
//吞吐为n,则用户线程执行时间 = n/n + 1,n=99,则99%的时间执行用户代码

开启自动调整内存大小(默认):

//JVM根据吞吐和STW最大时间自动调整堆大小
-XX:+UseAdaptiveSizePolicy

注意最大时间和吞吐两项是矛盾的,最大暂停时间极小,吞吐量又设置很大,后者可能得不到保证。添加如下JVM参数,程序启动的时候,打印所有配置项的值:

-XX:+PrintFlagsFinal

指定使用PS+PO组合,看下他们的默认配置:发现默认吞吐为99,即用户线程执行99%的时间,执行GC时间占1%,最大暂停时间则很大,相当于没有设置

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

PS:

//终端执行以下指令,打印默认参数配置项
java -XX:+PrintCommandLineFlags -version

在这里插入图片描述

6、G1垃圾回收器

关于G1:

  • G1,Garbage First,作用于年轻代+老年代
  • JDK9之后的默认垃圾回收器
  • 支持多CPU并行垃圾回收
  • 可设置最大停顿时间(最大的STW)
  • 算法:复制算法

优点:

  • 巨大的堆空间下回收也有高吞吐
  • 不会产生内存碎片

缺点:

  • JDK8之前带的G1还不够完善

G1前,堆内存的划分是连续的

在这里插入图片描述

G1下,堆被分为一块块大小相等的区Region:

  • 划分不再要求连续,比如下面两部分绿色的Eden
  • 每块面积默认 = 堆空间大小/2048
  • -XX:G1HeapRegionSize=32m可设置每块的大小,但值必须为2次幂,且在1~32之间(2、4、8、16、32)
  • 相对应的回收:年轻代回收(Young GC)、混合回收(Mixed GC)

在这里插入图片描述

使用G1需要添加JVM参数(JDK9及以后版本不用):

-XX:+UseG1GC

设置最大暂停时间:

-XX:MaxGCPauseMillis=毫秒值

7、G1的回收流程:Young GC

  • 对象新new出来依旧安置到年轻代的Eden区
  • G1判断出年轻代超过堆的max值(默认60%)时,触发Young GC
  • 标记Eden + 幸存区的存活对象
  • 根据开发者配置的最大暂停时间,选择性的将其中某一部分区域A中存活对象复制到新的幸存者区(对象头年龄+1),清空区域A

在这里插入图片描述

? 这个选择一部分区域的实现思路是:G1下的Young GC,会记录回收每个区Region(Eden和幸存者区)的平均耗时,做为下次回收的依据,默认-XX:MaxGCPauseMillis=200,如果计算每块Region平均耗时为40ms,则每次触发Young GC时,就最多干掉四块

  • 后续再触发GC,就是把Eden和上次的S区存活对象复制到另一块S区
  • 某个对象年龄到达阈值(默认15),依旧放入老年代
  • 此外,对象大小超过50%的Region空间时,直接进老年代(这类特殊的老年代叫Humongous区)

在这里插入图片描述

  • 多次回收后,当堆的使用率达到阈值,触发混合回收MixedGC,用复制算法回收一轮所有年轻代、部分老年代、大对象区
//阈值默认45%,可改
-XX:InitiatingHeapOccupancyPercent默认45%

在这里插入图片描述

8、G1的回收流程:Mixed GC

  • 初始标记(initial mark)
  • 并发标记(concurrent mark)
  • 最终标记(remark或者Finalize Marking)
  • 并发清理(cleanup)
    在这里插入图片描述

注意和CMS的区别,流程像,但干的活儿不一样,且最后清理时,采用的算法也不一样。此外,G1对老年代的清理会选择存活度最低的区域来进行回收,以保证回收效率。

在这里插入图片描述

最后,因为清理是复制算法,如果清理时发现没有空Region去存放转移的对象(没地儿复制了),则转为单线程执行标记-整理算法进行Full GC,此时会导致用户线程的暂停。

在这里插入图片描述

9、回收器的选择

JDK8及之前:

  • 关注暂停时间:选ParNew + CMS
  • 关注吞吐:Parallel Scavenge + Parallel Old

JDK9及以后:

  • G1,适用较大堆并且关注暂停时间
文章来源:https://blog.csdn.net/llg___/article/details/135407475
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。