你应该知道的Java垃圾收集器 - 串行、并行、CMS、G1

时间:2022-05-07
本文章向大家介绍你应该知道的Java垃圾收集器 - 串行、并行、CMS、G1,主要内容包括Garbage Collection JVM参数、JVM GC参数的使用示例:、基本概念、基础应用、原理机制和需要注意的事项等,并结合实例形式分析了其使用技巧,希望通过本文能帮助到大家理解应用这部分内容。

当我们谈论垃圾收集时,绝大多数人都知道这个概念,并在日常编程中使用它。即使如此,有关垃圾收集,我们很多人还是不太明白。关于JVM的一个最大的误解是它有一个垃圾收集器,其实它提供了四个不同的垃圾收集器,每一个都有自己独特的优点和缺点。重要的是,我们编程的时候可以通过JVM选择垃圾回收器类型。我们通过向JVM传递参数进行选择。每种类型在很大程度上有所不同并且可以为我们提供完全不同的应用程序性能。理解每种类型的垃圾回收器并且根据应用程序选择进行正确的选择是非常重要的。

这四种垃圾收集算法的共同点是,它们都是分代的,这意味着它们将堆分成不同的段,都使用由来已久的规则,就是堆中的大多数对象都是短暂(short lived )的,应该被快速回收。关于这方面的内容,我们以后会专门讨论。

1.串行收集器(Serial Collector)

串行收集器是最简单的,并且您可能不会使用它,因为它主要设计用于单线程环境(例如32位或Windows)和以及用于比较小的堆。此收集器会在自己工作的时候冻结所有应用程序线程,所以可能不适合服务器环境。它最适合的是简单的命令行程序。

通过JVM参数-XX:+UseSerialGC可以使用串行垃圾回收器。

2.并行/吞吐量收集器(Parallel / Throughput collector)

接下来是并行收集器,这是JVM的默认收集器。很像它的名字,它的最大的优点是使用多线程来扫描和压缩堆。并行收集器同样有个缺点就是在它执行 minor或者 full 垃圾回收时将会停止所有的应用程序线程。并行收集器最适合应用程序,可以容忍应用程序的暂停,并试图优化来降低收集器导致的CPU开销。

3. CMS收集器(CMS Collector)

接下来是并行收集器是CMS收集器(“并发标记扫描”)。此算法使用多个线程(“并发”)扫描整个堆(“mark”),以查找可以回收的未使用对象(“sweep”)。该算法将在两种情况下进入“stop the world”(STW)(文末有解释)模式:当初始化根(可以从线程入口点或静态变量访问的旧代中的对象)的初始标记时,以及当应用改变堆的状态的时候该收集器同时并发运行的时候,会强制cms再重新回去做一次执行来确保cms有正确的对象被标记。

使用此收集器时最大的问题是遇到“推荐失败(promotion failures)”,就是在收集年轻和老年代之间发生竞争状况(race condition)的情况。如果收集器需要将young objects推荐到old generation,但没有足够的时间来清除它,这时候将导致一个完整的STW收集 - 这是CMS收集器要特别要注意避免的。为了确保这不会发生,你可以将old generation的大小增加一点(或把整个堆的大小变大一点)或给收集器分配更多的后台线程,以便能超过“对象分配的速度“。

与并行收集器相比,该算法的另一个缺点是,它会使用更多的CPU,它通过使用多个线程执行扫描和收集,为应用程序提供更高水平的连续吞吐量。对于大多数长期运行的服务器应用程序,这种收集器不会让应用程序冻结,是一个合适的选择。即便如此,此算法默认情况下不启用。您必须指定XX:+ USeParNewGC才能实际启用它。如果你愿意分配更多的CPU资源,以避免应用程序暂停,并且你的堆小于4Gb的大小,就是适合使用此收集器。然而,如果堆大于4GB,那么你可能更适合最后一个算法--G1收集器。

4. G1收集器

在JDK 7 update 4 中引入的“第一收集器”(G1),是专门为更好地支持大于4GB的堆而设计的。 G1收集器利用多个后台线程来扫描堆,将其划分为多个区域,范围从1MB到32MB(取决于堆的大小)。 G1收集器首先会去扫描那些包含最多垃圾对象的区域,这种做法我们起名叫:Garbage first。此收集器使用-XX:+ UseG1GC标志打开。

这个收集器会出现STW的情况,就是在后台线程完成扫描未使用的对象之前堆被如果被耗尽的话,在这种情况下,收集器将不得不停止应用程序然后进入STW收集的状态。 G1还有另一个优点,就是它在执行的过程中可以顺便对堆进行压缩,这个能力CMS收集器只能在full STW收集期间才能做的。

最近的几年,大堆成了一个比较有争议的领域。越来越多的开发人员开始从单体化应用的那种一个机器一个JVM转移去开发更多的微服务,更多的基于模块的架构,这种情况下,每台机器上就会有多个JVM。

这是由许多因素驱动的,包括隔离应用程序的不同的部分,简化部署以及降低将应用程序的类们reloading到内存中的成本等等因素(在Java 8中实际已经又了许多改进)。即使如此,对JVM而言,最大的驱动因素之一就是希望避免那些长时间STW停顿(在对较大的堆中进行收集的时候,需要花很长时间)。 Docker等容器技术也加速了这一点,使您能够在相同的物理机器上轻松部署多个应用程序。

Java 8和G1收集器

另一个不错的改进就是Java 8 update 20的时候G1收集器开始支持字符串重复数据删除(String deduplication)。因为字符串(它们的内部是char []数组)占用了我们的大部分堆,所以进行了一个新的优化,使得G1收集器能够识别在整个堆中多次重复的字符串,并将它们纠正为指向同一个内部char[] 数组,以避免相同字符串的多个副本在堆内无效率地驻留。您可以使用-XX:+ UseStringDeduplicationJVM参数来尝试此操作。

Java 8和PermGen

Java 8中最大的变化之一是删除了传统上为类元数据、内部字符串和静态变量分配的堆的permgen部分。在这之前,一般都要求开发人员来优化和tuning。这也是多年来成为许多OutOfMemory异常的源头,所以这个优化对我们帮助很大。即使如此,这也不会减少开发人员将他们的应用程序解耦到多个JVM的微服务的浪潮。

每个收集器都设置了不同的一个切换和开关,每个都有自己的特点,选择哪一款还是取决于你具体的应用程序的具体要求。

Garbage Collection JVM参数

Option

Description

-XX:+UseSerialGC

Serial Garbage Collector

-XX:+UseParallelGC

Parallel Garbage Collector

-XX:+UseConcMarkSweepGC

CMS Garbage Collector

-XX:ParallelCMSThreads=

CMS Collector – number of threads to use

-XX:+UseG1GC

G1 Gargbage Collector

GC优化参数:

Option

Description

-Xms

Initial heap memory size

-Xmx

Maximum heap memory size

-Xmn

Size of Young Generation

-XX:PermSize

Initial Permanent Generation size

-XX:MaxPermSize

Maximum Permanent Generation size

JVM GC参数的使用示例:

java -Xmx12m -Xms3m -Xmn1m -XX:PermSize=20m -XX:MaxPermSize=20m

小卡片:什么是STW"stop-the-world" 机制简称STW,即,在执行垃圾收集算法时,Java应用程序的其他所有除了垃圾收集帮助器线程之外的线程都被挂起Java中一种全局暂停的现象全局停顿,所有Java代码停止,native代码可以执行,但不能和JVM交互多半由于GC引起Dump线程死锁检查堆DumpGC时为什么会有全局停顿?–类比在聚会时打扫房间,聚会时很乱,又有新的垃圾产生,房间永远打扫不干净,只有让大家停止活动了,才能将房间打扫干净。危害长时间服务停止,没有响应;遇到HA系统,可能引起主备切换,严重危害生产环境。

  • Java中一种全局暂停的现象
  • 全局停顿,所有Java代码停止,native代码可以执行,但不能和JVM交互
  • 多半由于GC引起
  • Dump线程
  • 死锁检查
  • 堆Dump
  • GC时为什么会有全局停顿?

–类比在聚会时打扫房间,聚会时很乱,又有新的垃圾产生,房间永远打扫不干净,只有让大家停止活动了,才能将房间打扫干净。

  • 危害
  • 长时间服务停止,没有响应;
  • 遇到HA系统,可能引起主备切换,严重危害生产环境。