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 来启用它。
除非你是单核服务器,不然不要选择它。这是它唯一的应用场景。
优点:
- 没有多线程切换成本,最高效
- 适合客户端、嵌入式等小型应用 缺点:
- 无法使用多核处理器
- 无法支持大应用
paralle-GC
这是一个多线程并发垃圾回收器,适用于吞吐量优先的应用,因为是多线程,所以它的速度比serialGC快N倍(N是垃圾回收线数,取决于cpu数量)
parallelGc适用于2GB-4GB大小的堆,这个大小的堆,回收时间还算ok。
JDK9以前的默认GC就是paralleGC,通过-XX:+UseParallelGC启用它。
优点:
- 充分利用多核处理器性能
- 在大堆上,比serialGC更高效
- 所有GC只能够,提供最高的吞吐
- 占用内存较小 缺点:
- 无法支持大堆
- 很长的暂停时间
Concurrent Mark Sweep (CMS) GC
Concurrent Mark Sweep GC 是一款低延迟GC,适用于老年代,旨在将重要级操作并发运行,争取不暂停应用,只有在部分无可避免的环节才暂停。这是大多数互联网和分布式应用使用GC。
通过-XX:+UseConcMarkSweepGC启用它。
优点:
- 第一款JDK中的低延迟GC,能应对10GB左右大小的堆
- 充分利用多核处理器资源
缺点:
- 内存碎片化问题严重,需要增大堆或者触发Full GC
- 存在浮动垃圾问题,需要经过多次GC才能处理
- 无法处理更大的堆
- 在JDK9中标记为过期,JDK14中移除
G1 (Garbage-First) GC
Garbage-First GC 区别于之前的GC,它没有采用一个年轻代+老年代的方式,而是使用了多个区域(region)的方式,每个region都有可能成为年轻代或者老年代。
G1GC 会更频繁得收集垃圾,这也是它叫做 Garbage-First的原因。G1GC 能处理相比CMS GC更大的堆。
通过-XX:+UseG1GC启用它。
优点:
- 均衡,综合考虑了暂停时间和吞吐量,JDK8以后选它基本没问题
- 首次引入自适应配置,自动调整新生代大小
- 可设定最大暂停时间,默认为200ms,一般可调整为50ms / 100ms
缺点:
- FULLGC是串行的,需要升级到JDK10以后才是并发的
ZGC
ZGC是oracle团队开发的最新的低延迟垃圾回收器,最新版本中暂停时间可以做到保证低于1ms。ZGC中的Z是终结的意思,这也表明了相关团队对他的期望。支持GB~TB大小的堆。
JDK11 中提供,JDK15中GA,JDK21中发布分代式ZGC。
通过-XX:+UseZGC启用它。
优点:
- 更短的暂停时间(jdk16以后保证 <1ms)
- 全自动配置,不需要指定新生代大小
- JDK21以后有新的分代模式,提升吞吐量 缺点:
- 整体吞吐量的损失,因为cpu更勤快得去做GC了
- 需要的jdk版本较高,JDK15后才正式发布
- JDK21之前都是无分代模式,吞吐量低
ShenandoahGC
ShenandoahGC比较有意思,它是红帽开发的低延迟GC,和G1GC还有ZGC是竞争的身份。能提供99%的延迟 <10ms 的体验。目前开发进度落后于ZGC。支持GB~TB大小的堆。
通过-XX:+UseShenandoahGC启用它。
优点:
- 相比G1GC更短的暂停时间,99% < 10ms
- 全自动配置,不需要指定新生代大小
- 因为不保证1ms级别暂停时间,吞吐量比ZGC优秀 缺点:
- 相对于G1GC,整体有吞吐量的损失
- 只有第三方发行版才带有该GC,oracle和openjdk都没有 (万恶的大公司政治斗争)
- 红帽开发,非oracle开发,没有官方背景
5.总结
对于互联网企业中的很多应用来说,基本都是高并发、高性能、高可用的,所以在核心服务上,有必要去做好GC的监控和调优,以持续为用户提供最好的服务。
希望本文能帮助到你选择合适的GC。
笔者所在公司,大部分应用使用CMS GC 和G1GC,少量极端追求低延迟的业务运行在ZGC和ShenandoahGC上。