一文看懂如何选择GC

565 阅读7分钟

1.简介

java 是一种基于虚拟机的,实现了内存自动化管理的语言,其中实现自动化内存管理的关键就是垃圾回收机制,垃圾回收机制负责对象的分配、无用对象的识别、无用对象的回收。可以说GC就是java语言比c/c++等语言门槛更低,更容易实现业务代码开发的关键功能。

JVM 默认提供了6种垃圾回收器,根据版本的不同,JDK会选择不同的垃圾回收器作为默认的垃圾回收器。根据我们应用情况的不同,我们可能就会触到到GC的瓶颈,这种情况,就需要我们了解GC并主动选择更适合我们的GC。

2.GC是什么

java 是一门垃圾回收语言,通过基于垃圾回收,我们不需要像C/C++中手工申请内存和释放内存,JVM替我们做了这一切。JVM会从系统申请内存,并在整个JVM进程内部单独构建一个管理系统来管理这些内存,JVM内存主要分为 heap 堆 和 stack 栈两部分。

根据GC假说,大多数的对象的存活时间都非常短。所以一部分GC根据对象存活时间、对象大小,将堆划分成了 年轻代(Eden) 和 老年代(Old). 年轻代上分配那些小并且快速无用的对象,老年代上分配存活时间长、体积大的对象。

垃圾回收器会持续跟踪所有对象的状态,检测它们是否存活,检测当前是否需要发起一次回收。当新生代的对象经过多次回收还存活时,就会被晋升到老年代。

所有的GC都是stop-the-world(下文缩写为STW)的,应用程序在此时会完整暂停,这也就是GC为什么那么重要的原因,它会影响我们的用户体验。

3.影响因素

堆大小

设定越大的堆,对象就越容易分配,但是这也会带来更长的回收时间。通过Xmx和Xms两个参数来设定堆的大小,Xmx对应堆最大值,Xms对应堆大小的初始值。

暂停时间

因为GC是STW的,所以GC暂停时间完全关键,它需要明显低于用户感知到的暂停时间。一般健康的暂停是10-50ms。

吞吐量

吞吐量是描述应用程序有多少时间在进行用户线程操作的指标,一般来说这个值高于99.99%最好

内存占用

GC需要记录各种对象的状态,还有其他空间换时间以减少暂停时间的操作,所以GC天然会占用内存。内存也是衡量GC性能的关键指标之一。

java版本

越新的版本能提供的GC能力就越强,因为包含了JVM本身的有优化和GC的优化。读者最好选择比较新的LTS版本。

应用延迟

应用延迟由GC暂停时间决定。

4.垃圾回收器

serial-GC

这是一个单线程串行垃圾回收器,只适用500MB-1GB大小的堆,因为更大的堆处理速度已经无法满足现代应用的要求了。

通过-XX:+UseSerialGC 来启用它。

除非你是单核服务器,不然不要选择它。这是它唯一的应用场景。

优点:

  1. 没有多线程切换成本,最高效
  2. 适合客户端、嵌入式等小型应用 缺点:
  3. 无法使用多核处理器
  4. 无法支持大应用

paralle-GC

这是一个多线程并发垃圾回收器,适用于吞吐量优先的应用,因为是多线程,所以它的速度比serialGC快N倍(N是垃圾回收线数,取决于cpu数量)

parallelGc适用于2GB-4GB大小的堆,这个大小的堆,回收时间还算ok。

JDK9以前的默认GC就是paralleGC,通过-XX:+UseParallelGC启用它。

优点:

  1. 充分利用多核处理器性能
  2. 在大堆上,比serialGC更高效
  3. 所有GC只能够,提供最高的吞吐
  4. 占用内存较小 缺点:
  5. 无法支持大堆
  6. 很长的暂停时间

Concurrent Mark Sweep (CMS) GC

Concurrent Mark Sweep GC 是一款低延迟GC,适用于老年代,旨在将重要级操作并发运行,争取不暂停应用,只有在部分无可避免的环节才暂停。这是大多数互联网和分布式应用使用GC。

通过-XX:+UseConcMarkSweepGC启用它。

优点:

  1. 第一款JDK中的低延迟GC,能应对10GB左右大小的堆
  2. 充分利用多核处理器资源

缺点:

  1. 内存碎片化问题严重,需要增大堆或者触发Full GC
  2. 存在浮动垃圾问题,需要经过多次GC才能处理
  3. 无法处理更大的堆
  4. 在JDK9中标记为过期,JDK14中移除

G1 (Garbage-First) GC

Garbage-First GC 区别于之前的GC,它没有采用一个年轻代+老年代的方式,而是使用了多个区域(region)的方式,每个region都有可能成为年轻代或者老年代。

G1GC 会更频繁得收集垃圾,这也是它叫做 Garbage-First的原因。G1GC 能处理相比CMS GC更大的堆。

通过-XX:+UseG1GC启用它。

优点:

  1. 均衡,综合考虑了暂停时间和吞吐量,JDK8以后选它基本没问题
  2. 首次引入自适应配置,自动调整新生代大小
  3. 可设定最大暂停时间,默认为200ms,一般可调整为50ms / 100ms

缺点:

  1. FULLGC是串行的,需要升级到JDK10以后才是并发的

ZGC

ZGC是oracle团队开发的最新的低延迟垃圾回收器,最新版本中暂停时间可以做到保证低于1ms。ZGC中的Z是终结的意思,这也表明了相关团队对他的期望。支持GB~TB大小的堆。

JDK11 中提供,JDK15中GA,JDK21中发布分代式ZGC。

通过-XX:+UseZGC启用它。

优点:

  1. 更短的暂停时间(jdk16以后保证 <1ms)
  2. 全自动配置,不需要指定新生代大小
  3. JDK21以后有新的分代模式,提升吞吐量 缺点:
  4. 整体吞吐量的损失,因为cpu更勤快得去做GC了
  5. 需要的jdk版本较高,JDK15后才正式发布
  6. JDK21之前都是无分代模式,吞吐量低

ShenandoahGC

ShenandoahGC比较有意思,它是红帽开发的低延迟GC,和G1GC还有ZGC是竞争的身份。能提供99%的延迟 <10ms 的体验。目前开发进度落后于ZGC。支持GB~TB大小的堆。

通过-XX:+UseShenandoahGC启用它。

优点:

  1. 相比G1GC更短的暂停时间,99% < 10ms
  2. 全自动配置,不需要指定新生代大小
  3. 因为不保证1ms级别暂停时间,吞吐量比ZGC优秀 缺点:
  4. 相对于G1GC,整体有吞吐量的损失
  5. 只有第三方发行版才带有该GC,oracle和openjdk都没有 (万恶的大公司政治斗争)
  6. 红帽开发,非oracle开发,没有官方背景

5.总结

对于互联网企业中的很多应用来说,基本都是高并发、高性能、高可用的,所以在核心服务上,有必要去做好GC的监控和调优,以持续为用户提供最好的服务。

希望本文能帮助到你选择合适的GC。

笔者所在公司,大部分应用使用CMS GC 和G1GC,少量极端追求低延迟的业务运行在ZGC和ShenandoahGC上。