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. 暂停时间的设置不要太小,会影响到吞吐量
目录
相关文章
|
8月前
|
Arthas 存储 算法
深入理解JVM,包含字节码文件,内存结构,垃圾回收,类的声明周期,类加载器
JVM全称是Java Virtual Machine-Java虚拟机JVM作用:本质上是一个运行在计算机上的程序,职责是运行Java字节码文件,编译为机器码交由计算机运行类的生命周期概述:类的生命周期描述了一个类加载,使用,卸载的整个过类的生命周期阶段:类的声明周期主要分为五个阶段:加载->连接->初始化->使用->卸载,其中连接中分为三个小阶段验证->准备->解析类加载器的定义:JVM提供类加载器给Java程序去获取类和接口字节码数据类加载器的作用:类加载器接受字节码文件。
768 55
|
监控 算法 Java
Java虚拟机(JVM)垃圾回收机制深度剖析与优化策略####
本文作为一篇技术性文章,深入探讨了Java虚拟机(JVM)中垃圾回收的工作原理,详细分析了标记-清除、复制算法、标记-压缩及分代收集等主流垃圾回收算法的特点和适用场景。通过实际案例,展示了不同GC(Garbage Collector)算法在应用中的表现差异,并针对大型应用提出了一系列优化策略,包括选择合适的GC算法、调整堆内存大小、并行与并发GC调优等,旨在帮助开发者更好地理解和优化Java应用的性能。 ####
339 27
|
10月前
|
存储 算法 Java
G1原理—5.G1垃圾回收过程之Mixed GC
本文介绍了G1的Mixed GC垃圾回收过程,包括并发标记算法详解、三色标记法如何解决错标漏标问题、SATB如何解决错标漏标问题、Mixed GC的过程、选择CollectSet的算法
G1原理—5.G1垃圾回收过程之Mixed GC
|
10月前
|
缓存 监控 算法
JVM简介—2.垃圾回收器和内存分配策略
本文介绍了Java垃圾回收机制的多个方面,包括垃圾回收概述、对象存活判断、引用类型介绍、垃圾收集算法、垃圾收集器设计、具体垃圾回收器详情、Stop The World现象、内存分配与回收策略、新生代配置演示、内存泄漏和溢出问题以及JDK提供的相关工具。
JVM简介—2.垃圾回收器和内存分配策略
|
8月前
|
缓存 算法 Java
JVM深入原理(八)(一):垃圾回收
弱引用-作用:JVM中使用WeakReference对象来实现软引用,一般在ThreadLocal中,当进行垃圾回收时,被弱引用对象引用的对象就直接被回收.软引用-作用:JVM中使用SoftReference对象来实现软引用,一般在缓存中使用,当程序内存不足时,被引用的对象就会被回收.强引用-作用:可达性算法描述的根对象引用普通对象的引用,指的就是强引用,只要有这层关系存在,被引用的对象就会不被垃圾回收。引用计数法-缺点:如果两个对象循环引用,而又没有其他的对象来引用它们,这样就造成垃圾堆积。
221 0
|
8月前
|
算法 Java 对象存储
JVM深入原理(八)(二):垃圾回收
Java垃圾回收过程会通过单独的GC线程来完成,但是不管使用哪一种GC算法,都会有部分阶段需要停止所有的用户线程。这个过程被称之为StopTheWorld简称STW,如果STW时间过长则会影响用户的使用。一般来说,堆内存越大,最大STW就越长,想减少最大STW,就会减少吞吐量,不同的GC算法适用于不同的场景。分代回收算法将整个堆中的区域划分为新生代和老年代。--超过新生代大小的大对象会直接晋升到老年代。
207 0
|
10月前
|
存储 缓存 算法
G1原理—3.G1是如何提升垃圾回收效率
本文深入探讨了G1垃圾回收器提升GC效率的核心机制,包括记忆集(RSet)、位图(BitMap)和卡表(CardTable)的设计与作用。记忆集通过记录跨代引用避免了不必要的老年代遍历,位图用于高效描述内存使用状态以优化标记过程,而卡表则在节约记忆集内存的同时提供更详细的引用信息。此外,文章还解析了DCQ(Dirty Card Queue)和DCQS(Dirty Card Queue Set)机制如何异步更新RSet,确保在高并发场景下的性能与准确性。这些设计共同提升了G1在标记、清理及整理内存时的效率。
511 10
|
10月前
|
存储 算法 Java
G1原理—6.G1垃圾回收过程之Full GC
本文详细探讨了G1垃圾回收器对Full GC(FGC)的优化处理,涵盖FGC的前置处理、整体流程及并行化改进。重点分析了传统FGC串行化的局限性以及G1通过Region分区和RSet机制实现并行标记的优势,包括任务窃取提升效率、跨分区压缩以生成空闲Region等技术细节。此外,文章还介绍了G1的新特性——字符串去重优化,通过判断char数组一致性减少重复字符串占用内存,从而提升内存使用效率。总结部分全面回顾了G1在FGC中的各项优化措施及其带来的性能改善。
G1原理—6.G1垃圾回收过程之Full GC
|
10月前
|
存储 算法 Java
G1原理—4.G1垃圾回收的过程之Young GC
本文详细解析了G1垃圾回收器中YGC(Young Generation Collection)的完整流程,包括并行与串行处理阶段。内容涵盖YGC相关参数设置、YGC与Mixed GC及FGC的关系、新生代垃圾回收的具体步骤(如标记存活对象、复制到Survivor区、动态调整Region数量等),以及并行阶段的多线程操作和串行阶段的关键任务(如处理软引用、整理卡表、重构RSet)。
G1原理—4.G1垃圾回收的过程之Young GC
|
机器学习/深度学习 监控 算法
Java虚拟机(JVM)的垃圾回收机制深度剖析####
本文深入探讨Java虚拟机(JVM)的垃圾回收机制,揭示其工作原理、常见算法、性能调优策略及未来趋势。通过实例解析,为开发者提供优化Java应用性能的思路与方法。 ####
317 28