盘点Java中的那些常用的Garbage Collector

简介: Java是一门面向对象的语言。在使用Java的过程中,会创建大量的对象在内存中。而这些对象,需要在用完之后“回收”掉,释放内存资源。这件事我们称它为垃圾收集(Garbage Collection,简称GC),而实际执行者就是各种各样的“垃圾收集器”(Garbage Collector,以下也简称GC)。

GC总览


Java是一门面向对象的语言。在使用Java的过程中,会创建大量的对象在内存中。而这些对象,需要在用完之后“回收”掉,释放内存资源。这件事我们称它为垃圾收集(Garbage Collection,简称GC),而实际执行者就是各种各样的“垃圾收集器”(Garbage Collector,以下也简称GC)。

为什么会有各种各样的GC?因为时代在发展,以前的GC可能不能满足现在的需求,所以就会有源源不断的GC推出来。先来看一下都有哪些主流的GC:

新生代:

Serial:单线程,新生代;

ParNew: 多线程,新生代;

Parallel Scavenge:多线程,新生代,关注吞吐量;

老年代:

Serial Old: 单线程,Serial的老年代版本;

Parallel Old:多线程,Parallel Scavenge的老年代版本,关注吞吐量;

CMS:多线程,标记-清除算法,关注停顿时间,可以与Serial和ParNew配合。

其它

G1:同时负责新生代和老年代,是目前一段时间主流的垃圾收集器(JDK 9到现在JDK 16的默认垃圾收集器)。

ZGC:在大堆下也可以控制STW时间极短(几毫秒内),在JDK 11 为实验阶段,在JDK 15转正。Oracle发起,2017年贡献给OpenJDK。

Shenandoah GC:停顿时间极短,在JDK 12为试验阶段,在JDK 15转正。Red Hat发起,与ZGC和G1是竞争关系。

STW: Stop The World,指的是停止用户线程。GC应该尽量避免STW或者缩减STW的时间。


各JDK版本默认GC


下面来看一下从JDK 7开始在默认GC(JDK 7之前的,我就不考古了,现在大家项目上用的也少)。

  • 在JDK 7,默认是Parallel Scavenge + Serial Old。
  • 在JDK 8 及JDK 7u4之后的版本,默认是Parallel Scavenge + Parallel Old。
  • 在JDK 9 到JDK 16,默认是G1

严格来说之前的版本应该是JDK 1.7, JDK 1.8,这里及下文为了表述方便,我就写成了JDK 7, JDK 8。


聊聊几种主流的GC


考虑到现在大家用的Java版本主要都是JDK 8起,所以主要聊JDK 8及之后可能会用到的GC。

Parallel Scavenge + Parallel Old

以下简称为PS,PO

PS + PO是JDK 7u4之后一直到JDK 9之前server模式都在用的默认GC组合。

PS用于新生代,是一个并行的多线程收集器,使用了复制算法。它关注吞吐量,可以通过参数设置最大GC停顿时间、吞吐量的大小等。

PS收集器相比于ParNew来说,多了一个“自适应调节策略”。当+UseAdaptiveSizePolicy这个参数打开之后,就不需要手动指定新生代的大小,Eden和Survivor区的比例,晋升老年代对象等细节参数了,虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间或者最大吞吐量。

PO用于老年代,同样是关注吞吐量。它是一个多线程的收集器,采用“标记-整理”算法。PO是在JDK 1.6之后才提供的,目的就是为了与PS配合。在此之前,PS只能与Serial Old配合(因为框架不适配的原因,不能与CMS配合),老年代GC效率低下,所以PO应运而生。

ParNew + CMS

ParNew是一个新生代多线程收集器,使用复制算法。它的出现比PS更早,主要是为了替代Serial单线程低下的效率。在新生代复制期间,使用多线程来降低STW的时间。

CMS是JDK 5才推出的一款关注停顿时间的GC。与PO不同的是,CMS使用的是“标记-清除”算法,这能够让它实现更小的停顿时间,但代价是会产生大量的空间碎片,可能会在大对象晋升老年代时由于没有连续的内存空间,触发full gc。

了解到这里,其实我有两个疑问。

1 CMS为什么不使用“标记-整理”算法?

参考《并发垃圾收集器(CMS)为什么没有采用标记-整理算法来实现?》这篇文章(原文是在iteye的一个讨论,但现在已经找不到了)的说法。GC代码和用户代码需要保持同步,才能保证两者观察到的对象图是一致的。而保持同步有两种做法,一种是read barrier,一种是write barrier。因为读远大于写,所以CMS使用的是write barrier。这就导致CMS如果用“标记-整理”算法的话,需要在“整理”的时候STW,而如果使用“标记-清除”算法的话,在“清除”阶段是不用STW的。

2 为什么CMS从来没作为默认垃圾收集器?

参考知乎这篇文章《为什么 JDK 8 默认使用 Parallel Scavenge 收集器?》的回答。总结有三个原因:

  • Java使用服务端的场景为主,服务端更专注吞吐量,所以JDK 8默认的是PS + PO;
  • 使用、调优很复杂,有高达70多个参数
  • “后浪”太优秀,CMS比G1早不了多少,5开始加入,6成熟,9被标记弃用,14被删除。而G1是7加入,8成熟,9正式成为默认。

然而事实上,有很多互联网大厂选择了CMS+ParNew的组合。前段时间美团出了一篇文章《Java中9种常见的CMS GC问题分析与解决》,阿里也用得比较多,所以还是可以了解一下。

G1

前面提到了,G1是一个优秀的后浪。从JDK 9到当前JDK 16一直都是默认的GC。相较于之前的几款GC来说,G1可以说是有一些颠覆性的设计,比如Region、Card Table、Remember Set等。

关于G1,需要一篇单独的文章来介绍。我之前在个人网站上有一篇文章介绍,《JVM - G1》

文章地址:yasinshaw.com/articles/82

在我的个人网站yasinshaw.com,文章页面搜索“G1”即可搜到。

这里大致介绍一下它的特点:

  • 低延迟优先,即主要侧重于响应能力;
  • 与CMS相同的地方在于,它们都属于并发收集器,在大部分的收集阶段都不需要挂起应用程序;
  • 更精确的预测GC停顿时间,可以根据-XX:MaxGCPauseMillis参数指定停顿时间;
  • 收缩空闲空间不会造成由长GC引起的应用停顿时间;
  • G1没有CMS的碎片化问题(或者说不那么严重)。

ZGC

关于ZGC,笔者了解得也不多。ZGC在JDK 11推出实验版,在JDK 15成为正式版。ZGC的目标是在尽量不牺牲吞吐量(官方宣称,目标是对程序吞吐量影响小于15%)的情况下,做到极低的停顿时间,并且停顿时间不会随着对的内存大小变大而升高。

虽然ZGC在JDK 15已经转正,但还是在不断完善和迭代。JDK 16最近发布,在这个版本中ZGC46 个功能增强以及25 个 bug 修复。已经可以做到平均暂停时间约为 50 微秒(0.05 毫秒),最大暂停时间约为 500 微秒(0.5 毫秒)。暂停时间不受堆、活动集和根集大小的影响。这个停顿时间,对业务的影响可以说已经微乎其微了,我等只能大呼NB。

想要深入了解ZGC原理的同学,可以参考美团技术的这篇文章《新一代垃圾回收器ZGC的探索与实践》(只找到知乎官方号的链接)。

Shenandoah GC

按理说G1和ZGC已经很牛逼了,为什么还有其它的GC出来?学不动了啊,有木有。。。

Shenandoah GC,我们称它为SGC吧,它是Red Hat发起的一款GC,与ZGC是竞争关系。Shenandoah更像是G1的继承者,有很多相同之处。它跟ZGC一样,目标也是低停顿时间,不过实现原理有些不同,ZGC是基于colored pointers来实现,而Shenandoah GC是基于brooks pointers来实现。

SGC没有承诺停顿时间小于10ms,也没有说要牺牲吞吐量(但实际吞吐量有没有降低就不知道了)。在官方的性能测试图来看,停顿时间也是极低的。

网络异常,图片无法展示
|

GC(3) Pause Init Mark 0.771ms GC(3) Concurrent marking 76480M->77212M(102400M) 633.213ms GC(3) Pause Final Mark 1.821ms GC(3) Concurrent cleanup 77224M->66592M(102400M) 3.112ms GC(3) Concurrent evacuation 66592M->75640M(102400M) 405.312ms GC(3) Pause Init Update Refs 0.084ms GC(3) Concurrent update references  75700M->76424M(102400M) 354.341ms GC(3) Pause Final Update Refs 0.409ms GC(3) Concurrent cleanup 76244M->56620M(102400M) 12.242ms

它只有在标记和更新引用的时候会暂停,看起来也是只有几毫秒的停顿时间。


总结一下


现在市面上很大一部分Java程序都还是基于Java 8(甚至有些是Java 7的),所以PS + PO,和ParNew + CMS都是可以了解一下的,毕竟工作中很有可能会用到。尤其是CMS,大厂用得很多,面试基本上必问。

ZGC和SGC,虽然在JDK 15都已经成为正式版,但实际生产中使用很少,有些潜在问题不一定能很好地发现,可以慢慢去了解和尝试。

可以预见的是,G1会在未来很长一段时间内成为最主流的垃圾收集器,所以很有必要好好了解一下G1,做好知识储备。


目录
相关文章
|
2月前
|
存储 监控 算法
Java中的内存管理:理解Garbage Collection机制
本文将深入探讨Java编程语言中的内存管理,着重介绍垃圾回收(Garbage Collection, GC)机制。通过阐述GC的工作原理、常见算法及其在Java中的应用,帮助读者提高程序的性能和稳定性。我们将从基本原理出发,逐步深入到调优实践,为开发者提供一套系统的理解和优化Java应用中内存管理的方法。
|
3月前
|
监控 算法 Java
Java中的内存管理:理解Garbage Collection机制
本文将深入探讨Java编程语言中的内存管理,特别是垃圾回收(Garbage Collection, GC)机制。我们将从基础概念开始,逐步解析垃圾回收的工作原理、不同类型的垃圾回收器以及它们在实际项目中的应用。通过实际案例,读者将能更好地理解Java应用的性能调优技巧及最佳实践。
103 0
|
3月前
|
监控 算法 Java
深入理解Java中的垃圾回收机制在Java编程中,垃圾回收(Garbage Collection, GC)是一个核心概念,它自动管理内存,帮助开发者避免内存泄漏和溢出问题。本文将探讨Java中的垃圾回收机制,包括其基本原理、不同类型的垃圾收集器以及如何调优垃圾回收性能。通过深入浅出的方式,让读者对Java的垃圾回收有一个全面的认识。
本文详细介绍了Java中的垃圾回收机制,从基本原理到不同类型垃圾收集器的工作原理,再到实际调优策略。通过通俗易懂的语言和条理清晰的解释,帮助读者更好地理解和应用Java的垃圾回收技术,从而编写出更高效、稳定的Java应用程序。
|
6月前
|
算法 Java
垃圾回收机制(Garbage Collection,GC)是Java语言的一个重要特性,它自动管理程序运行过程中不再使用的内存空间。
【6月更文挑战第24天】Java的GC自动回收不再使用的内存,关注堆中的对象。通过标记-清除、复制、压缩和分代等算法识别无用对象。GC分为Minor、Major和Full类型,针对年轻代、老年代或整个堆进行回收。性能优化涉及算法选择和参数调整。
80 3
|
5月前
|
开发框架 缓存 Java
浅析JAVA日志中的性能实践与原理解释问题之"Garbage Free"技术的实现方式问题如何解决
浅析JAVA日志中的性能实践与原理解释问题之"Garbage Free"技术的实现方式问题如何解决
|
6月前
|
算法 Java
Java垃圾回收(Garbage Collection,GC)是Java虚拟机(JVM)的一种自动内存管理机制,用于在运行时自动回收不再使用的对象所占的内存空间
【6月更文挑战第18天】Java的GC自动回收内存,包括标记清除(产生碎片)、复制(效率低)、标记整理(兼顾连续性与效率)和分代收集(区分新生代和老年代,用不同算法优化)等策略。现代JVM通常采用分代收集,以平衡性能和内存利用率。
74 3
|
7月前
|
存储 算法 Java
「译文」Java 垃圾收集参考手册(七):Garbage First
「译文」Java 垃圾收集参考手册(七):Garbage First
|
7月前
|
算法 Java
Java内存管理,什么是垃圾回收机制(Garbage Collection)?
Java内存管理,什么是垃圾回收机制(Garbage Collection)?
47 1
|
并行计算 算法 安全
Java 8 - 自定义Collector
Java 8 - 自定义Collector
156 0
怎么在java中创建一个自定义的collector
怎么在java中创建一个自定义的collector
怎么在java中创建一个自定义的collector