JVM深入学习(二十一)-主流垃圾回收器G1

简介: G1(Garbage First) 是一款并行回收的,新生代/老年代都回收的全功能垃圾回收器

G1垃圾回收器

1.1 概述

G1(Garbage First) 是一款并行回收的,新生代/老年代都回收的全功能垃圾回收器

G1的思想是区域分代化,垃圾优先

区域分代化: 将堆内存分为一个一个的region,每个region可以是物理上不连续的空间,G1对region进行追踪,衡量每个region回收后的价值和回收所需时间(其实就是region回收的效率,回收后能清除较多空间的region优先级更高)

垃圾优先: 由于G1对垃圾回收的效率更加敏感,因此称G1是垃圾优先

G1的出现基于现代计算机资源的升级,cpu和内存不断增大,为了满足在资源充足的情况下的 低暂停时间和吞吐量 两者兼顾的目标

G1的目标就是: 在保证吞吐量的前提下,尽可能的减少暂停时间.

G1在jdk1.9中被设置为默认垃圾回收器,在jdk1.7/1.8中需要使用jvm参数启用

1.2 优点

  1. 并行和并发: G1同时兼顾并行和并发两种方式
  2. 分代收集: G1也属于分代收集算法,但是与传统分代不同的是,G1使用region作为分代的区域,region不在要求每个代在堆内存的连续性和数量.即每个region都是一个小的分代区域,但是region又不固定,可能一个Eden的region在完全回收后,下次这个region就变为老年代.
  3. 堆空间整理: 不存在内存碎片问题; region之间的回收是基于复制算法的,整体看G1的堆内存回收是基于标记-压缩算法,都不存在内存碎片问题,而且当堆空间越大,基于region的G1的优势越明显
  4. 可控制的暂停时间:
  1. G1可以设置一个停顿时间,尽可能的达到这个停顿时间.
  2. G1在回收region的时候是根据region的回收价值来排序,优先回收价值最高的region,就能保证最大的回收效率
  3. 拿CMS来对比,G1的回收效率可能比不过CMS最好的时候的延时停顿,但是相比于CMS的最差情况(串行回收),无疑好很多.
  1. 在其他垃圾回收器(ParNew/CMS)中,多线程的操作是基于jvm的内置线程的,而G1可以借助于应用程序线程.

1.3 缺点

  1. 在小内存(6-8g下)的场景下,效率并不一定超过CMS
  2. G1在垃圾回收上耗费的内存及保持程序运行的额外内存都要高于CMS


1.4 G1垃圾回收器相关jvm参数


1.4.1 指定使用G1垃圾回收器

-XX:+UseG1GC

启用G1垃圾回收器,在jdk9之后就不再指定了,默认都是G1

1.4.2 指定Region的大小

-XX:G1HeapRegionSize

指定每个Region的大小,一般为2的幂MB,例如: 2MB 4MB 8MB 16MB 32MB

默认值为堆内存的 1/2000

目的是将堆内存分为 2048 个区域.

例如: 当大小设置为1时, 堆内存就是2G 大小为2则堆内存为4G

1.4.3 指定期望的最大停顿时间

-XX:MaxGCPauseMillis

这个参数用于设置期望的最大停顿时间,jvm会尽可能保证达到这个停顿时间,但是不一定能每次回收都达到这个时间,只能尽可能保证高概率的达到这个停顿时间(90%)

参数默认值为 200ms

如果此参数修改的过小,可能会导致的结果: 比如设置为20ms,G1中有很多个region,默认值200ms可以回收10个region,但是20ms就只能回收1个region,如果此时内存占用速度较高,就会导致region回收的速度跟不上清理的速度,久而久之,当内存不够用时就会触发FullGC,反而增大了停顿时间,所以此参数修改要慎重.

1.4.4 指定并发的STW时的工作线程数

-XX:ParallelGCThreads

并发STW的垃圾回收线程数,最多为8,和CMS中的设置一样

1.4.5 指定并发标记的线程数

-XX:ConcGCThreads

设置并发标记的线程数,默认为-XX:ParallelGCThreads的1/4

1.4.6 指定触发垃圾回收的堆占用阈值

-XX:InitiatingHeapOccupAncyPercent

指定触发垃圾回收的java堆占用率阈值,超过此值出发垃圾回收 类似CMS中的参数

默认值为45

调整此参数策略也可以参考CMS中的解释.

1.4 jdk8中使用G1的步骤

目前jdk8应该还是企业主流版本,因此jdk8的垃圾回收器调优有以下几个步骤(G1):

  1. 使用jvm参数指定启用G1垃圾回收器
  2. 指定期望的最大停顿时间
  3. 指定堆内存大小

其他的就由jvm自动调整就可以,再细节的调优需要根据具体情况调整.

1.5 适用场景

  1. 大内存,多核处理器的硬件环境下的服务端应用(毕竟是保证吞吐量的前提)
  2. 即需要低延迟,有需要吞吐量的大内存应用
  3. 针对替换CMS的场景
  1. 超过一半的内存在活跃状态
  2. 对象分配频率和年代提升频率非常高
  3. GC停顿时间过长(0.5-1s)

1.6 region详解

G1使用region跳出了分代垃圾回收的大框架,开启了分区回收的新概念,其中region是最重要的一部分.

传统分代垃圾回收要求每代都是连续的内存空间

而G1将每个代对应的内存空间都拆分到一个一个region上,那么region其实就不再要求内存上的连续性了.

1.6.1 region的分类

G1中将region分为四类

  1. Eden 新生代的伊甸园区
  2. Survivor 新生代的幸存者区
  3. Old 老年代
  4. Humongous 存放大对象的区域

其中新生代/老年代的都不难理解,就是将粒度拆分的更细而已.

而新增了一个H区,这个类型主要用来存放大对象,大对象的定义为超过1.5个region

为什么新增这个大对象呢?

在原来的垃圾回收器中,如果一个新对象新生代放不下,那么就会直接进入老年代存放,但是如果这个大对象是一个生命周期很短的对象时,就存在问题,老年代的回收耗时要比新生代更加多.因此在G1中新增H区来处理大对象

注: 当一个H区存放不下一个大对象时,就必须寻找连续的H区来存放.

1.6.2 region的分配

首先 对G1来说,region的分配就是空闲列表的方式,因为region之间内存不连续,我们可以把region理解为一个个碎片,这个时候只能采用空闲列表的方式来分配.

对于region内部的分配来说就分为两种情况:

1.6.2.1 指针碰撞

region内部就是指针碰撞的方式来分配,region内部的垃圾回收算法是基于复制算法的,因此不存在碎片问题,直接使用指针碰撞就可以

1.6.2.2 TLAB

我们之前在堆内存的学习中看到过,堆的内存配置里有一小块是TLAB

主要是为了多线程的并发情况

region内部也存在TLAB,可以通过这样的方式分配内存.

1.7 G1垃圾回收的详细过程

1.7.1 主要环节

主要分为三个环节和一个后备环节

  1. 年轻代回收 YoungGC
  2. 老年代并发标记 Concurrent Mark
  3. 混合回收 Mixed STW
  4. 后备FullGC(单线程,独占式)

简单说明:

  1. 年轻代的回收其实跟其他gc没有区别,当年轻代内存不足时,开始回收年轻代的region,然后根据年龄计算是放到幸存者区还是进入老年代.这里指的年轻代是所有的年轻代的region合计内存,也就是说即使G1是分区的,但在分代的角度所有的region还是用分代的理念解释的. 此外,年轻代使用的是复制算法,因此是独占式的回收,需要STW.
  2. 老年代并发标记是标记可达对象的一个过程,这个过程是并发的,因此不影响用户线程执行,当堆内存达到阈值(默认45%)时,开始并发标记过程
  3. 在老年代并发标记完成后,就开始混合回收. G1的老年代回收与其他垃圾回收器不一样,不用回收整个老年代,他的回收也是根据region来的,我们在上文中也提到了一个最大停顿时间的参数,根据整个参数G1会调整回收region的数量,以达到设置的最大停顿时间(默认200ms),并且新生代也可以在整个环节进行回收(混合回收)


  1. 后备方案,就是当G1的老年代回收失败时,就会启用单线程的FullGC来做整个jvm的回收工作,以确保jvm的正常运行,出现FullGC的时候可能就等待时间较长.


1.7.2 记忆集 Remembered Set

记忆集记录了每个region被其他region引用的情况.

记忆集的出现主要是为了解决 跨代引用 问题

跨代引用是指: 新生代的对象被老年代的对象引用

如果出现了跨代引用,那么在垃圾回收的时候,怎么寻找GCRoots? 比如当新生代的对象被老年代的对象引用的时候,回收新生代的时候,怎么寻找该对象是否存在引用,将老年代全部遍历一遍也可以实现,但是开销太大.

记忆集就是为了解决这个问题的.

给每个region维护一个记忆集,记录每个region被其他region引用的情况,然后在垃圾回收的时候把记忆集作为GCRoots的一部分,就可以做到跨代引用然后进行垃圾回收.


1.7.3 YoungGC

  1. 当Eden内存不足时,会触发YoungGC
  2. YoungGC会回收Eden和Survivor区


当YoungGC开始时,首先STW,然后创建回收集(Collection Set),记录Eden和Survivor所有的内存分段.

YoungGC详细过程:

  1. 扫描GCRoots 获取根引用和Rset(记忆集)作为GCRoots
  2. 更新Rset 处理dirty card queue队列中的card,来更新Rset,保证Rset的最新;
  1. dirty card queue是在引用赋值的时候放入card的一个队列,这个card记录了对象的详细引用信息,用于保证Rset是最新的引用信息
  2. 使用dirty card queue的目的就是因为Rset存在多线程问题,如果在赋值的时候就直接更新Rset,可能存在锁的问题,开销更大.
  1. 处理Rset 识别Rset中被老年代对象引用的新生代对象,这些对象被认为是存活对象.
  2. 复制对象 采用复制算法将region放入到空闲的region中,这个过程中同时会考虑对象的年龄,如果对象的年龄达到阈值,则会进入老年代.
  3. 处理引用 处理软/弱/虚/终结器等引用信息.

YoungGC完成后,不存在内存碎片问题,且当复制对象完成后,原来的region会被作为空闲region放入空闲列表中等待使用.

1.7.4 老年代并发标记环节

这个过程类似CMS的并发标记过程,有一些区别,可以理解为G1采用了CMS的并发回收过程,且进行区域回收的改进.

具体流程如下:

  1. 初始标记 标记GCRoot直接引用的可达对象,这个阶段STW,并会触发一次YoungGC
  2. 根区域扫描 扫描Survivor区引用的老年代对象,并标记老年代中被引用的对象(视为可达对象),这个过程需要在YoungGC之前完成
  3. 并发标记 类似CMS,根据初始标记的结果进行并发标记,此时垃圾回收线程和用户线程并行执行,但是可能会被YoungGC打断,在并发标记阶段会同时计算该区域对象的存活率(可达对象占比),另外如果该区域所有对象都是垃圾对象,那么会直接进行清理,不用等待混合回收环节
  4. 再次标记 类似CMS,修正并发标记环节再次复活的对象.
  5. 独占清理计算 计算各个区域的存活对象占比,垃圾回收占比并排序,为下个环节混合回收做数据支持,因为计算需要准确性所以是STW的,不会真的回收垃圾
  6. 并发清理 清理完全空闲的区域.

1.7.5 混合回收

混合回收就是根据并发标记环节计算得出的回收占比,再根据最大停顿时间来回收一定数量的region,同时也会有YoungGC

混合回收有一些说明

  1. 默认情况下老年代要分8次回收(jvm参数-XX:G1MixedGCCountTarget控制)
  2. 混合回收的内容包括
  1. 1/8的老年代内存段
  2. Eden区
  3. Survivor区
  1. 老年代的1/8的内存段是根据垃圾占比排序出来的,优先回收垃圾占比高的内存分段.
  2. 老年代内存分段的回收也有一个阈值控制,默认65%(jvm参数-XX:G1MixedGCLiveThresholdPercent控制),当内存分段垃圾占比低于65%时,不会回收,因为回收的开销过大,复制算法存存活对象越多,开销越大.
  3. 老年代内存也可能进行不到8次,G1允许堆内内存有10%(jvm参数-XX:G1HeapWastePercent)的浪费,这就意味着如果进行7次或者更少的垃圾回收之后,堆内垃圾占比小于10%,那么就不会再次回收了,开销很大,回收的垃圾很少,划不来.

1.7.6 FullGC(并非G1每次垃圾回收必经阶段)

FullGC是单线程,独占式的垃圾回收过程,再开发和调优的过程中要尽量避免FullGC的出现.

FullGC的出现有两种情况:

  1. G1的回收都是基于复制算法的,将一个region复制到另外空闲的region,如果没有空闲的region的时候,就会进行FullGC
  1. 当堆内存过小的时候可能出现这个问题
  2. 当停顿时间设置的过小,而堆内存占用速度又过快的时候可能出现这个问题
  1. G1是存在并发标记阶段的,在这个阶段用户线程和垃圾回收线程交替执行,如果用户线程在这个时候没有足够的内存使用,就会触发FullGC.

1.8 G1的调优

首先G1在回收阶段(YoungGC和混合回收阶段)都是STW的,G1并没有并发回收的特性,但是在ZGC上可能会存在这个特性.

调优的几个注意点:

  1. 不要设置年轻代的大小 在使用-Xmn -XX:NewRatio等参数设置年轻代的大小后,年轻代的大小就被固定,不能由G1动态调节,而YoungGC又是独占式的回收,如果年轻代的大小固定,那么就会出现期望最大暂停时间参数被覆盖的问题,因为G1不能根据设置的期望最大暂停时间去调节年轻代的大小了.
  2. 暂停时间的设置不要太小,会影响到吞吐量
目录
相关文章
|
26天前
|
监控 算法 Java
Java虚拟机(JVM)的垃圾回收机制深度解析####
本文深入探讨了Java虚拟机(JVM)的垃圾回收机制,旨在揭示其背后的工作原理与优化策略。我们将从垃圾回收的基本概念入手,逐步剖析标记-清除、复制算法、标记-整理等主流垃圾回收算法的原理与实现细节。通过对比不同算法的优缺点及适用场景,为开发者提供优化Java应用性能与内存管理的实践指南。 ####
|
18天前
|
监控 算法 Java
Java虚拟机(JVM)垃圾回收机制深度剖析与优化策略####
本文作为一篇技术性文章,深入探讨了Java虚拟机(JVM)中垃圾回收的工作原理,详细分析了标记-清除、复制算法、标记-压缩及分代收集等主流垃圾回收算法的特点和适用场景。通过实际案例,展示了不同GC(Garbage Collector)算法在应用中的表现差异,并针对大型应用提出了一系列优化策略,包括选择合适的GC算法、调整堆内存大小、并行与并发GC调优等,旨在帮助开发者更好地理解和优化Java应用的性能。 ####
25 0
|
17天前
|
存储 监控 算法
Java虚拟机(JVM)垃圾回收机制深度解析与优化策略####
本文旨在深入探讨Java虚拟机(JVM)的垃圾回收机制,揭示其工作原理、常见算法及参数调优方法。通过剖析垃圾回收的生命周期、内存区域划分以及GC日志分析,为开发者提供一套实用的JVM垃圾回收优化指南,助力提升Java应用的性能与稳定性。 ####
|
21天前
|
机器学习/深度学习 监控 算法
Java虚拟机(JVM)的垃圾回收机制深度剖析####
本文深入探讨Java虚拟机(JVM)的垃圾回收机制,揭示其工作原理、常见算法、性能调优策略及未来趋势。通过实例解析,为开发者提供优化Java应用性能的思路与方法。 ####
31 1
|
24天前
|
监控 算法 Java
Java虚拟机垃圾回收机制深度剖析与优化策略####
【10月更文挑战第21天】 本文旨在深入探讨Java虚拟机(JVM)中的垃圾回收机制,揭示其工作原理、常见算法及参数调优技巧。通过案例分析,展示如何根据应用特性调整GC策略,以提升Java应用的性能和稳定性,为开发者提供实战中的优化指南。 ####
40 5
|
26天前
|
存储 算法 安全
JVM常见面试题(四):垃圾回收
堆区域划分,对象什么时候可以被垃圾器回收,如何定位垃圾——引用计数法、可达性分析算法,JVM垃圾回收算法——标记清除算法、标记整理算法、复制算法、分代回收算法;JVM垃圾回收器——串行、并行、CMS垃圾回收器、G1垃圾回收器;强引用、软引用、弱引用、虚引用
|
24天前
|
存储 算法 Java
JVM进阶调优系列(10)敢向stop the world喊卡的G1垃圾回收器 | 有必要讲透
本文详细介绍了G1垃圾回收器的背景、核心原理及其回收过程。G1,即Garbage First,旨在通过将堆内存划分为多个Region来实现低延时的垃圾回收,每个Region可以根据其垃圾回收的价值被优先回收。文章还探讨了G1的Young GC、Mixed GC以及Full GC的具体流程,并列出了G1回收器的核心参数配置,帮助读者更好地理解和优化G1的使用。
|
25天前
|
监控 Java 测试技术
Elasticsearch集群JVM调优垃圾回收器的选择
Elasticsearch集群JVM调优垃圾回收器的选择
46 1
|
24天前
|
算法 Java
JVM有哪些垃圾回收算法?
(1)标记清除算法: 标记不需要回收的对象,然后清除没有标记的对象,会造成许多内存碎片。 (2)复制算法: 将内存分为两块,只使用一块,进行垃圾回收时,先将存活的对象复制到另一块区域,然后清空之前的区域。用在新生代 (3)标记整理算法: 与标记清除算法类似,但是在标记之后,将存活对象向一端移动,然后清除边界外的垃圾对象。用在老年代
22 0
|
2月前
|
缓存 算法 Java
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS
这篇文章详细介绍了Java虚拟机(JVM)中的垃圾回收机制,包括垃圾的定义、垃圾回收算法、堆内存的逻辑分区、对象的内存分配和回收过程,以及不同垃圾回收器的工作原理和参数设置。
77 4
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS