JVM垃圾收集—垃圾收集器及常见组合参数

Java
292
0
0
2022-12-22

文章目录

  • Serial
  • ParNew
  • Parallel Scavenge
  • Serial Old
  • Parallel Old
  • CMS (Concurrent Mark Sweep)
  • G1
  • 理解吞吐量和停顿时间
  • 如何选择合适的垃圾收集器呢

img

首先我们要知道垃圾收集器有三种类型:

串行收集器 Serial 和 Serial Old 只能有一个垃圾回收线程执行,用户线程暂停。(适用于内存较小的嵌入式设备)

并行收集器[吞吐量优先] Paraller Scanvenge、Parallel Old 多条垃圾收集线程并行工作,但此时用户线程仍然处于等待阶段。(适用于科学计算、后台处理等若干交互场景)

并发收集器[停顿时间优先] CMS、G1 用户线程和垃圾收集线程同时执行(但并不一定是并行的,可能是交替执行的),垃圾收集线程在执行的时候不会停顿用户线程的运行。(适用于相对时间有要求的场景,比如WEB)

我按照发展顺序给大家介绍一下:

img

Serial

  • 复制算法
  • 新生代
  • 单线程收集器

特点:它只会使用一个CPU或者一条收集线程去完成垃圾收集工作,更重要的是其在垃圾收集的时候需要暂停其他线程。

优点:简单高效

缺点:收集过程需要暂停所有线程。

应用:Client模式下的默认新生代收集器(Serial收集器是最基本、发展历史最悠久的收集,之前(JDK1.3.1之前)是虚拟机新生代收集器的唯一选择。)

img

ParNew

  • 复制算法
  • 新生代
  • 多线程收集器

特点:ParNew收集器实质上是Serial收集器的多线程并行版本,除了同时使用多条线程进行垃圾收集之外,其余的行为包括Serial收集器可用的所有控制参数(例如:-XX:SurvivorRatio、-XX: PretenureSizeThreshold、-XX:HandlePromotionFailure等)、收集算法、Stop The World、对象分配规 则、回收策略等都与Serial收集器完全一致,在实现上这两种收集器也共用了相当多的代码。

优点:多CPU时,比Serial效率更高。

缺点:收集过程暂停所有的应用程序,单CPU核心时比Serial效率差。

应用:运行在Server模式下的虚拟机中首选的新生代收集器。

img

Parallel Scavenge

此收集器与吞吐量关系密切,故也称为吞吐量优先收集器。

  • 复制算法
  • 新生代
  • 多线程收集器
  • 关注吞吐量

特点:多线程

Parallel Scavenge收集器使用两个参数控制吞吐量: 控制最大的垃圾收集停顿时间 XX:MaxGCPauseMillis 直接设置吞吐量的大小 XX:GCTimeRatio

吞吐量 = 运行用户代码时间 / 运行用户代码时间 + 运行垃圾收集时间。 如果虚拟机完成某个任务,用户代码加上垃圾收集器总共耗时100分钟,其中垃圾收集器花费了1分钟,那吞吐量就是 99 / 100= 99%。 吞吐量越大,意味着垃圾收集的时间更短、则用户代码可以充分利用CPU资源,尽快完成程序的运算任务。

这里不是你设置了就一定有效,虚拟机会尽可能的靠近你设置的数值,并不是绝对一致

Serial Old

Serial Old 是Serial 收集器的老年代版本

  • 标记整理
  • 老年代
  • 单线程收集器

img

Parallel Old

  • 标记整理
  • 老年代
  • 多线程收集器
  • 关注吞吐量

img

特点:多线程,采用标记-整理算法。

应用场景:注重高吞吐量以及CPU资源敏感的场合

CMS (Concurrent Mark Sweep)

是一种以获取最短回收停顿时间为目标的收集器

  • 标记清除
  • 老年代
  • 并发收集器
  • 关注最短停顿时间

应用场景:适用于注重服务的响应速度,希望系统停顿时间最短,给用户带来良好的体验。比如web服务,b/s结构。

工作分为四步: 第一步、初始标记(STW),标记GC Roots能直接关联到的对象,速度非常快。

第二步、并发标记,进行GC Roots Tracing ,就是从GC Roots开始找到它能引用的所有对象的过程。

第三步、重新标记(STW),为了修成并发标记期间因用户程序继续运作导致标记产生变动的一部分对象的标记记录。这个阶段的停顿时间一般会比初始标记阶段稍长一些,但远比并发标记的时间要短。

第四步、并发清除,在整个过程中耗时最长的并发标记和并发清除过程,收集器线程都可以与用户线程一起工作,因此,从总体上看,CMS收集器的内存回收过程与用户线程一起并发执行的

img

优点:并发收集、并发清除、低停顿。

缺点:对CPU要求高,无法处理浮动垃圾、产生大量空间碎片、并发阶段会降低吞吐量。

  1. 对CPU敏感,并发阶段虽然不会导致用户线程暂停,但是它总是要线程执行,还是会占用CPU资源,(一定程度上也是,吞吐量的下降)
  2. 无法处理浮动垃圾:在最后一步并发清理过程中,用户线程执行也会产生垃圾,但是这部分垃圾是在标记之后,所以只有等到下一次 gc 的时候清理掉。
  3. 产生大量空间碎片、并发阶段会降低吞吐量。

这里有一个刁钻的面试问题: CMS默认晋升老年代为6的原因: 简单来说,CMS对内存尤其敏感,且会导致单线程Serial FullGC 这个是非常严重的后果,而从结果上说越大的MaxTenuringThreshold会更快的导致heap的碎片化(不光old 区,首先要明白对于内存的分配并不是真的一个对象一个对象紧密排列的),所以历代CMS 默认这个值都会比较小(JDK8以前是4,之后调整为6)

G1

  • 分代收集(仍然保留分代的概念)
  • 并行与并发
  • 老年代和新生代
  • 关注最短停顿时间
  • 内存分为Region[ˈriːdʒən] 区(内存是否连续)
  • 可以设置最短停顿时间

工作也是分为四步: 第一步、初始标记(STW),标记GC Roots能直接关联到的对象(速度很快)。

第二步、并发标记,进行 GC Roots Tracing,就是从GC Roots开始找到它能引用的所有其他对象的过程。

第三步、最终标记(STW),为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分的标记记录。这个阶段的停顿时间一般会比初始标记阶段稍微长,但是要比并发标记要短。

第四步、筛选回收(STW),对各个Region的回收价值和成本进行排序,根据用户所期望的GC停顿时间指定回收计划。

img

G1 总结: JDK 7 开始使用,JDK8非常成熟,JDK9默认的垃圾收集器。

如果停顿时间过短,会造成频繁垃圾回收,会导致OOM:GC overhead limitexceeded (超过98%的时间用来做GC并且回收了不到2%的堆内存时会抛出此异常)

虚拟机怎么判断是否需要使用G1收集器? 答:50%以上的堆被存活对象占用、对象分配和晋升的速度变化非常大、垃圾回收时间较长。

理解吞吐量和停顿时间

停顿时间 = 垃圾收集器进行垃圾回收的执行时间 吞吐量 = 运行用户代码时间 / 运行用户代码时间 + 运行垃圾收集时间。

如果虚拟机完成某个任务,用户代码加上垃圾收集器总共耗时100分钟,其中垃圾收集器花费了1分钟,那吞吐量就是 99 / 100= 99%。 吞吐量越大,意味着垃圾收集的时间更短、则用户代码可以充分利用CPU资源,尽快完成程序的运算任务。

停顿时间越短就越适合需要和用户交互的程序,良好的响应速度能提升用户体验; 高吞吐量则可以高效地利用CPU时间,尽快完成程序的运算任务,主要适合在后台运算而不 需要太多交互的任务。

吞吐量和停顿时间是衡量垃圾回收器的标准,我们进行调优也是观察这两个变量。

如何选择合适的垃圾收集器呢

这个准则只能参考,因为性能取决于堆的大小,应用程序维护的实时数据量以及可用处理器的数量和速度

大白话:牛逼哄哄的硬件设备不怎么需要调优,垃圾设备才考验你的调优技能。

如果应用程序的内存在100M左右,使用串行收集器 -XX:+UseSerialGC。

如果是单核心,并且没有停顿要求,默认收集器,或者选择带有选项的-XX:+UseSerialGC

如果允许停顿时间超过1秒或者更长时间,默认收集器,或者选择并行-XX:+UseParallelGC

如果响应时间最重要,并且不能超过1秒,使用并发收集器 -XX:+UseConcMarkSweepGC or -XX:+UseG1GC

1.8默认的垃圾回收:PS + ParallelOld

  • -XX:+UseSerialGC = Serial New (DefNew) + Serial Old 小型程序。默认情况下不会是这种选项,HotSpot会根据计算及配置和JDK版本自动选择收集器
  • -XX:+UseParNewGC = ParNew + SerialOld 这个组合已经很少用(在某些版本中已经废弃) 为什么废弃的官方解释: 链接: Why Remove support for ParNew+SerialOld and DefNew+CMS in the future?
  • -XX:+UseConc(urrent)MarkSweepGC = ParNew + CMS + Serial Old
  • -XX:+UseParallelGC = Parallel Scavenge + Parallel Old (1.8默认) 【PS +SerialOld】
  • -XX:+UseParallelOldGC = Parallel Scavenge + Parallel Old
  • XX:+UseG1GC = G1