虚拟机系列:JVM中的垃圾收集器

2,899 阅读13分钟
Java与C++之间有一堵由内存动态分配和垃圾收集技术所围成的”高墙“,墙外的人想进去,墙内的人却想出来
​
---- 《深入理解Java虚拟机》

之前的文章有介绍内存区域以及jvm中的垃圾回收算法,今天介绍下他们的具体实现的-垃圾收集器。在Hotspot虚拟机发展的过程,针对各种场景开发出来的不同的垃圾收集器,每种垃圾收集器都有其自己的特点和使用位置。

图中展示了7种作用于不同分代的收集器,如果两个收集器之间存在连线,则说明它们可以搭配使用。虚拟机所处的区域则表示它是属于新生代还是老年代收集器。

新生代收集器:SerialParNewParallel Scavenge。 ---- 全是复制算法

老年代收集器:CMS(标记-清除),Serial Old(标记-复制),Parallel Old(标记-整理)

整堆收集器:G1(一个Region中是标记-清除算法,2个Region之间是复制算法)

先认识几个名词:

1,并行(Parallel) :多个垃圾收集线程并行工作,此时用户线程处于等待状态。

2,并发(Concurrent) :用户线程和垃圾收集线程同时执行。

3,吞吐量:运行用户代码时间/(运行用户代码时间+垃圾回收时间)。

4,STW(Stop The World) :暂停所有的用户工作线程。

1. Serial收集器

单线程,新生代收集器,使用复制算法,存在STW问题

Serial收集器是最基本,发展历史最悠久的收集器。

特点: 简单高效(相比于其他收集器),对于限定单个CPU的环境来说,Serial收集器由于没有现场交互的开销,专心做垃圾回收自然可以获得最高的单线程收集效率。但是问题是单线程不能满足现在多CPU的机器下使用,而且此收集器在进行垃圾收回的时候会暂停所有用户的工作线程,也就是说存在STW的问题(其他收集器现在也没有解决)。

应用场景: 对于运行在Client模式下的虚拟机来说是最好的选择

2. ParNew收集器

多线程,新生代收集器,使用复制算法,存在STW问题

ParNew收集器其实就是Serial收集器的多线程版本,除了使用多线程进行垃圾收集外,其余的行为都是一样的,代码也有很多复用的,包括控制参数,收集算法,Stop The World,对象分配规则,回收策略等。

特点: 主要就是多线程的,在多CPU系统中是比较有优势的,而且默认开启收集线程数与CPU的数量一致,在CPU非常多的环境中,可以使用-XX:ParallelGCThreads参数来限制垃圾收集的线程数。和Serial收集器一样存在Stop The World的问题。

应用场景: ParNew收集器是许多运行在Server模式下的虚拟机中首选的新生代收集器,因为它是除了Serial收集器外,唯一一个能与CMS收集器配合工作的。

3. Parallel Scavenge 收集器

GC自适应调节策略,多线程,新生代收集器,使用复制算法,存在STW问题

Parallel Scavenge 收集器与吞吐量关系密切,所以也可以成为吞吐量优先收集器。提供了两个参数来精确的控制吞吐量,分别是控制最大垃圾收集停顿时间:-XX:MaxGCPauseMillis和直接设置吞吐量大小:XX:GCRatio

特点: Parallel Scavenge 的目的是达到一个可控的吞吐量,除了上面的两个参数外还提供了一个开关参数置-XX:+UseAdptiveSizePolicy实现 GC自适应调节策略(这个也是与ParNew的一个重要区别)。

GC自适应调节策略 : 通过参数-XX:+UseAdptiveSizePolicy控制,开关打开后,不需要手动的指定新生代的大小(-Xmn)、Eden与Survivor区的比例(-XX:SurvivorRation)、晋升老年代的对象年龄(-XX:PretenureSizeThreshold)等参数,虚拟机根据当前系统的运行情况收集性能监控信息,动态的调整这些参数以提供最合适的停顿时间或者最大的吞吐量。也就说使用PS收集器省事,开关打开,让系统自己看着办。

4. Serial Old收集器

单线程,新生代收集器,使用标记整理算法,存在STW问题

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

特点: 同样是单线程的收集器,采用的标记整理算法,其它和Serial没有多少差别了

运行示意图和Serial一样的

5. Parallel Old收集器

GC自适应调节策略,多线程,老年代收集器,使用标记整理算法,存在STW问题

特点: Parallel Old是Parallel Scavenge收集器的老年代版本,使用多线程和标记整理算法。

应用场景: 注重高吞吐量以及CPU资源敏感的场合,都可以优先考虑Parallel Scavenge+Parallel Old 收集器。

运行示意图和Parallel Scavenge一样的

6. CMS收集器

老年代收集器,使用标记清除算法,并发低停顿

CMS(Concurrent Mark Sweep) 收集器是一种以获取最短回收停顿时间为目标的收集器。

特点: 如上

应用场景: 适用于注重服务的响应速度,希望系统停顿时间最短,给用户带来更好的体验等场景下。如web程序、b/s服务。

CMS运行的过程:

  • 初始标记: 标记GC Roots能直接关联到的对象,速度很快但是仍存在Stop The World问题。
  • 并发标记: 进行GC Roots Tracing 的过程,找出存活对象且用户线程可并发执行。
  • 重新标记: 为了修正并发标记期间因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录。仍然存在Stop The World问题。
  • 并发清除: 对标记的对象进行清除回收。

过程中的并发标记和并发清除是和用户线程一起并发执行的。

CMS收集器的缺点:

  • 对CPU资源非常敏感。虽然并发阶段不会暂停用户线程,但是会占用部分线程或者说CPU资源,总吞吐量降低。
  • 无法处理浮动垃圾。并发清理阶段用户线程还在运行,这期间会产生新的垃圾,可能出现Concurrent Model Failure失败而导致另一次Full GC的产生。
  • 采用标记-清除算法所以会存在空间碎片的问题,导致大对象无法分配空间,不得不提前触发一次Full GC。

7. G1 收集器

并行并发,分代收集,空间整合,可预测停顿

G1收集器(Garbage First)一款面向服务端应用的垃圾收集器。从分代上看G1依然属于分代收集器,有新生代,老年代,还有eden区,survivor区。G1收集器在整个堆进行分区,划分成一个个的区域,每次收集的时候只收集几个区域,来控制垃圾回收的停顿时间。如下是G1在整个堆中区域划分(不再是物理上划分,而是逻辑上划分)

特点:

  • 并行性: G1在收集期间,可以由多个GC线程同时工作,有效的利用多核计算能力
  • 并发性: G1拥有与应用程序交替执行的能力,部分工作可以和应用程序同时执行,可以避免在整个回收期间阻塞应用程序。
  • 分代收集: G1能够独自管理整个Java堆,并且采用不同的方式去处理新创建的对象和已经存活了一段时间、熬过多次GC的旧对象以获取更好的收集效果。
  • 空间整合: G1运作期间不会产生空间碎片,收集后能提供规整的可用内存。
  • 可预测的停顿: G1除了追求低停顿外,还能建立可预测的停顿时间模型。能让使用者明确指定在一个长

度为M毫秒的时间段内,消耗在垃圾收集上的时间不得超过N毫秒。整个堆划分多个小区域就是为了能够方便控制回收的时间。

G1收集器的过程有 初始标记,并发标记,最终标记,筛选回收,前几个步骤和CMS是一样的。

  • 初始标记: 标记GC Roots能直接关联到的对象,速度很快但是仍存在Stop The World问题。

  • 并发标记: 从 GC Root 开始对堆中的对象进行可达新分析,找出存活对象,这个阶段耗时较长,但可以和用户线程并发执行。

  • 最终标记: 修正在并发标记阶段因用户程序执行而产生变动的标记记录。存在停顿用户线程(STW)的问题。

  • 筛选回收: 筛选回收阶段会对各个 Region 的回收价值和成本进行排序,根据用户所期望的 GC 停顿时间来指定回收计划(用最少的时间来回收包含垃圾最多的区域,这就是 Garbage First 的由来——第一时间清理垃圾最多的区块),这里为了提高回收效率,并没有采用和用户线程并发执行的方式,而是停顿用户线程。

G1相关参数

-XX:+UseG1GC 打开G1的开关。

-XX:ParallelGCThreads 设置并行GC时进行内存回收的线程数。

我们常用的jdk版本还停留在1.7和1.8,这时候G1收集器已经开始商用了,只是实际环境中用的比较少,从jdk1.9开始已经把G1作为默认的垃圾收集器了。

8. 垃圾收集器相关参数

  1. -XX:+UseSerialGC: 虚拟机运行在CLient模式下的默认值,打开后,使用Serial + Serial Old的收集器组合进行内存回收。
  2. -XX:+UseParNewGC: 打开后,使用ParNew + Serial Old 的收集器组合进行内存回收。
  3. -XX:+UseConcMarkSweepGC: 打开后。使用 ParNew + CMS + Serial Old的收集器组合进行内存回收。Serial Old收集器作为CMS收集器出现Concurrent Mode Failure失败后的后备收集器使用的。
  4. -XX:+UseParallelGC: 虚拟机运行在client模式下的默认值,使用Parallel Scavenge+Serial Old(PS MarkSweep)的收集器组合进行内存回收。
  5. -XX:+UserParallelOldGC: 使用Parallel+Scavenge+Parallel Old的收集器组合进行内存回收。
  6. -XX:SurvivorRatio: 新生代中Eden区域与Survivior区域的容量比值,默认为8,代表Eden:Survicor = 8:1
  7. -XX:PretenureSizeThreshold: 直接晋升老年代对象的大小,设置这个参数后,大于这个参数的对象将直接在老年代分配。
  8. -XX:MaxTenuringThreshold: 晋升到老年代的对象年龄,每个对象在坚持一次Minor GC之后,年龄就+1,当超过这个参数值时就进入老年代。
  9. -XX:UseAdaptiveSizePolicy: 动态调整java堆中各个区域的大小以及进入老年代的年
  10. -XX:HandlePromotionFailure: 是否允许分配担保失败,即老年代的剩余空间不足以应付新生代的整个Eden和survivor区的所有对象都存活的极端情况。
  11. -XX:ParallelGCThreads: 设置并行GC时进行内存回收的线程数。
  12. -XX:GCTimeRatio: GC时间占总时间的比率,默认值为99,即允许1%的GC时间。仅在使用Parallel Scavenge 收集器时生效。
  13. -XX:MaxGCPauseMillis: 指定目标最大停顿时间。仅在使用Parallel Scavenge 收集器时生效
  14. -XX:CMSInitiatingOccupancyFraction: 设置CMS 收集器在老年代空间被使用多少后触发垃圾收集。默认值为68%,仅在使用CMS 收集器时生效
  15. -XX:UseCMSCompactAtFullCollection: 设置CMS 收集器在完成垃圾收集后是否要进行一次内存碎片整理。仅在使用CMS 收集器时生效
  16. -XX:CMSFullGCsBeforeCompaction: 设置CMS 收集器在进行若干次垃圾收集后再启动一次内存碎片整理。仅在使用CMS 收集器时生效

现在还有新一代的收集器:ZGC ,这个是JDK 11中推出的一款低延迟垃圾回收器,详细可以参考美团的新一代垃圾回收器ZGC的探索与实践

总结-GC收集器的选择:

除非应用程序有非常严格的暂停时间要求,否则请先运行应用程序并允许VM选择收集器(如果没有特别要求。使用VM提供给的默认GC就好)。如有必要,调整堆大小以提高性能。 如果性能仍然不能满足目标,请使用以下准则作为选择收集器的起点:

  • 如果应用程序的数据集较小(最大约100 MB),则选择带有选项-XX:+UseSerialGC的串行收集器。
  • 如果应用程序将在单个处理器上运行,并且没有暂停时间要求,则选择带有选项-XX:+UseSerialGC的串行收集器。
  • 如果峰值应用程序性能是第一要务,并且没有暂停时间要求或可接受一秒或更长时间的暂停,则让VM选择收集器或使用-XX:+UseParallelGC选择并行收集器 。
  • 如果响应时间比整体吞吐量更重要,并且垃圾收集暂停时间必须保持在大约一秒钟以内,则选择具有-XX:+UseG1GC。(值得注意的是JDK9中CMS已经被Deprecated,不可使用!移除该选项)
  • 如果使用的是jdk8,并且堆内存达到了16G,那么推荐使用G1收集器,来控制每次垃圾收集的时间。
  • 如果响应时间是高优先级,或使用的堆非常大,请使用-XX:UseZGC选择完全并发的收集器。(值得注意的是JDK11开始可以启动ZGC,但是此时ZGC具有实验性质,在JDK15中才取消实验性质的标签,可以直接显示启用,但是JDK15默认GC仍然是G1)。

这些准则仅提供选择收集器的起点,因为性能取决于堆的大小,应用程序维护的实时数据量以及可用处理器的数量和速度。如果推荐的收集器没有达到所需的性能,则首先尝试调整堆和新生代大小以达到所需的目标。 如果性能仍然不足,尝试使用其他收集器

总体原则:减少STOP THE WORD时间,使用并发收集器(比如CMS+ParNew,G1)来减少暂停时间,加快响应时间,并使用并行收集器来增加多处理器硬件上的总体吞吐量。

原文地址


我是纪先生,用输出倒逼输入而持续学习,持续分享技术系列文章,以及全网值得收藏好文,欢迎关注公众号,做一个持续成长的技术人。

个人网站

JVM虚拟机系列历史文章

1. 虚拟机系列:jvm运行时堆内存如何分代;

2. 虚拟机系列:jvm中的垃圾回收算法;

3. 虚拟机系列:jvm运行时数据区域;

4. 虚拟机系列:JVM中对象的创建,内存布局和访问定位;