JVM工作原理与实战(二十七):堆的垃圾回收-G1垃圾回收器

简介: JVM作为Java程序的运行环境,其负责解释和执行字节码,管理内存,确保安全,支持多线程和提供性能监控工具,以及确保程序的跨平台运行。本文主要介绍了G1垃圾回收器、G1垃圾回收器的回收方式、G1垃圾回收器执行流程、垃圾回收器的选择等内容。

一、垃圾回收器

垃圾回收器是Java虚拟机(JVM)中的重要组件,负责自动管理内存,回收不再使用的对象所占用的空间。了解垃圾回收器的种类、工作原理以及如何根据应用场景选择合适的垃圾回收器,对于提高应用程序的性能和稳定性至关重要。

垃圾回收器通过自动检测和回收不再被引用的对象,以释放内存空间,避免内存泄漏。为了实现这一目标,垃圾回收器采用了一系列算法来识别和回收无用对象。这些算法主要包括标记清除复制标记整理分代垃圾回收等。

垃圾回收器分为年轻代和老年代,它们各自负责不同生命周期的对象的回收。除了G1垃圾回收器外,其他垃圾回收器必须成对组合使用,以确保整个堆内存的有效管理。


二、G1垃圾回收器介绍

G1垃圾回收器(Garbage-First Garbage Collector)是JDK 9及之后版本的默认垃圾回收器。它的设计目标是将Parallel Scavenge和CMS两种垃圾回收器的优点相结合,以提供更好的性能表现。Parallel Scavenge关注吞吐量,允许用户设置最大暂停时间,但会减少年轻代可用空间的大小。相比之下,CMS关注暂停时间,但在吞吐量方面会有所下降。而G1垃圾回收器旨在解决这些问题,以实现更高效和可预测的垃圾回收性能。

G1垃圾回收器的设计特点:

  • 支持巨大的堆空间回收,并保证较高的吞吐量:通过将堆划分为多个相等的区域(Region),G1能够更有效地管理内存,并在回收过程中优先处理含有大量垃圾的对象区域。
  • 支持多CPU并行垃圾回收:G1采用并发收集方式,充分利用多核CPU的计算能力,提高垃圾回收的效率。这使得它在处理大规模数据和高负载应用程序时表现出色。
  • 允许用户设置最大暂停时间:通过调整相关参数,用户可以控制垃圾回收过程中的最大停顿时间,以满足应用程序的性能需求。

G1垃圾回收器采用了独特的内存管理策略,将整个堆内存划分为多个大小相等的区域,称为Region。这些Region不需要连续存储,提供了更大的灵活性。根据应用程序的需求,这些Region可以配置为Eden区(伊甸园区)、Survivor区(幸存者区)和Old区(老年代),以满足不同的回收需求。与之前的垃圾回收器相比,G1的出现改变了传统的内存管理方式。在G1之前,内存结构一般是连续的,而G1通过将堆内存划分为多个Region,打破了这种连续性。这种设计使得G1能够更好地适应现代计算机系统的内存布局和分配需求,提高了内存利用率和应用程序的性能。

image.gif

在G1垃圾回收器中,Region的大小是通过堆空间大小除以2048来计算得到的,也可以通过参数-XX:G1HeapRegionSize=来明确指定Region的大小。需要注意的是,Region size必须是2的指数幂,其取值范围是从1M到32M。

-XX:G1HeapRegionSize=

image.gif

G1垃圾回收器采用复制算法进行垃圾回收,该算法将活跃对象从一个内存区域复制到另一个内存区域,从而实现内存空间的回收。这种算法确保了内存碎片化最小化,提高了空间利用率和应用程序的性能。

G1垃圾回收器的优点:

  • 延迟可控性:对于较大的堆空间,如超过6G的堆,G1垃圾回收器能够保持较低的延迟,确保应用程序的响应性和性能。
  • 内存碎片最小化:G1通过复制算法进行垃圾回收,避免了内存碎片的产生,从而提高了内存的利用率和应用程序的性能。
  • 并发标记的SATB算法:G1使用高效的并发标记的SATB算法,该算法在标记阶段对应用程序的影响较小,确保了高吞吐量。
  • 多CPU并行处理:G1充分利用多核CPU的计算能力,通过并行处理提高垃圾回收的效率,特别是在处理大规模数据和高负载应用程序时。

G1垃圾回收器在吞吐量、延迟控制和内存管理方面具有显著优势。它适用于处理大规模数据和高负载应用程序的场景,尤其适用于对延迟敏感的应用程序和需要高效利用内存资源的应用。但是G1垃圾回收器在JDK 8之前还不够成熟,在JDK 8最新版本和JDK 9及更高版本中,建议默认使用G1作为垃圾回收器,可以使用-XX:+UseG1GC参数来启用G1垃圾回收器,JDK9之后默认不需要启用。

-XX:+UseG1GC

image.gif

三、G1垃圾回收器详解

1.G1垃圾回收器的回收方式

G1垃圾回收器主要采用了两种垃圾回收方式:年轻代回收(Young GC)和混合回收(Mixed GC)。

年轻代回收(Young GC):主要针对Eden区和Survivor区中不再使用的对象进行回收。这个过程会导致应用程序线程暂停(Stop-The-World)。G1垃圾回收器提供了一种灵活的机制,允许开发人员通过参数-XX:MaxGCPauseMillis=n(默认值为200毫秒)来设定每次垃圾回收时的最大暂停时间。这个参数的设置有助于优化应用程序的性能,确保垃圾回收过程对应用程序的影响降至最低。

-XX:MaxGCPauseMillis=n

image.gif

混合回收(Mixed GC)

G1垃圾回收器的混合回收包括以下步骤:

  1. 初始标记(Initial Mark):这一步主要标记GC Roots引用的对象为存活。GC Roots是垃圾回收的起始点,通常是活跃的对象。
  2. 并发标记(Concurrent Mark):在这一阶段,垃圾回收器会并发地遍历堆中的对象图,将初始标记阶段标记为存活的对象引用的对象也标记为存活。
  3. 最终标记(Final Mark or Remark):此步骤会再次检查并标记在并发标记阶段可能被漏标的对象,确保所有存活的对象都被正确地标记。同时,任何不再关联的对象也会被标记。
  4. 并发清理(Cleanup):这一步骤将存活的对象复制到其他Region,确保没有内存碎片的产生。G1垃圾回收器通过这种方式优化内存使用,并提高后续的垃圾回收效率。

image.gif

G1垃圾回收器对老年代的清理策略是选择存活度最低的区域进行回收,这样可以高效地回收内存,这也是G1(Garbage first)名称的由来。在清理阶段,G1使用复制算法,确保内存碎片的最小化。这种混合回收策略允许G1在不影响应用程序性能的情况下有效地管理Java堆的内存。

案例(回收红色区域)

image.gif

FULL GC:当G1垃圾回收器在执行清理阶段时,如果发现没有足够的空闲Region来存放需要转移的对象,就会触发Full GC,它会暂停所有的用户线程并使用单线程执行标记整理算法进行内存整理。这种操作对应用程序的性能影响较大,因此应尽量避免。为了预防Full GC的发生,开发者应确保分配给应用程序的堆内存有适当的预留空间,避免堆内存过度使用。这样可以减少Full GC的发生频率,并提高应用程序的稳定性和性能。

2.G1垃圾回收器执行流程

年轻代回收:

  • 1.对象分配与判断:新创建的对象首先会被放置在Eden区。G1垃圾回收器通过监控年轻代的使用情况,当判断年轻代区域已满(超过60%容量)时,触发Young GC。
  • 2.存活对象标记:在执行Young GC时,G1首先会精确地标记出Eden和Survivor区域中的存活对象。
  • 3.对象复制与区域清空:根据预设的最大暂停时间和其他配置参数,G1选择某些区域,将存活对象复制到一个新的Survivor区(对象的年龄加1),并清空这些区域。


  • 4.性能记录与优化:在执行Young GC过程中,G1垃圾回收器会记录每次回收时每个Eden区和Survivor区的详细耗时数据。这些数据为下次回收提供了宝贵的参考,帮助G1更精确地计算出在给定的最大暂停时间内可以回收的Region数量。例如,如果配置的-XX:MaxGCPauseMillis为n(默认200),每个Region的回收耗时为40ms,那么在一次回收中,G1最多能处理4个Region。
  • 5.循环与移动:后续的Young GC过程与之前相似,Survivor区中的存活对象会被移动到另一个Survivor区。
  • 6.老年代与Humongous区:当某个对象的年龄达到预设阈值(默认15)或其大小超过一个Region的一半时,该对象会被移入老年代。这些老年代被称为Humongous区。例如,在堆内存为4G、每个Region为2M的环境中,任何超过1M的对象都会被放入Humongous区。如果对象过大,可能会跨越多个Region。


混合回收:

  • 7.触发条件与处理:随着时间的推移,老年代中会出现多个区域。当总堆占有率达到预设阈值(-XX:InitiatingHeapOccupancyPercent默认45%)时,G1会触发混合回收(Mixed GC)。这种回收会处理所有年轻代和部分老年代的对象以及大对象区。混合回收采用复制算法来完成,确保高效的内存回收。
-XX:InitiatingHeapOccupancyPercent

image.gif

3.垃圾回收器的选择

对于JDK 8及更早版本:

  • ParNew + CMS:这个组合关注垃圾回收时的暂停时间。ParNew作为年轻代回收器,而CMS用于老年代回收。这种组合适合对暂停时间有严格要求的场景。
  • Parallel Scavenge + Parallel Old:这个组合主要关注吞吐量。Parallel Scavenge用于年轻代,Parallel Old用于老年代。这种组合适合对吞吐量有较高要求的场景。
  • G1(JDK 8之前不建议):尽管G1在JDK 8之前并不推荐使用,但在JDK 8及更早版本中,如果堆大小较大并且关注暂停时间,可以考虑使用G1。

对于JDK 9及更高版本:

  • G1:随着G1的日趋成熟,从JDK 9开始,G1已成为默认的垃圾回收器。因此,建议在生产环境中使用G1,以充分利用其性能和功能。

总结

JVM是Java程序的运行环境,负责字节码解释、内存管理、安全保障、多线程支持、性能监控和跨平台运行。本文主要介绍了G1垃圾回收器、G1垃圾回收器的回收方式、G1垃圾回收器执行流程、垃圾回收器的选择等内容,希望对大家有所帮助。

相关文章
|
12天前
|
存储 算法 Java
散列表的数据结构以及对象在JVM堆中的存储过程
本文介绍了散列表的基本概念及其在JVM中的应用,详细讲解了散列表的结构、对象存储过程、Hashtable的扩容机制及与HashMap的区别。通过实例和图解,帮助读者理解散列表的工作原理和优化策略。
28 1
散列表的数据结构以及对象在JVM堆中的存储过程
|
1月前
|
缓存 算法 Java
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS
这篇文章详细介绍了Java虚拟机(JVM)中的垃圾回收机制,包括垃圾的定义、垃圾回收算法、堆内存的逻辑分区、对象的内存分配和回收过程,以及不同垃圾回收器的工作原理和参数设置。
62 4
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS
|
1月前
|
存储 监控 算法
美团面试:说说 G1垃圾回收 底层原理?说说你 JVM 调优的过程 ?
尼恩提示: G1垃圾回收 原理非常重要, 是面试的重点, 大家一定要好好掌握
美团面试:说说 G1垃圾回收 底层原理?说说你 JVM 调优的过程  ?
|
29天前
|
监控 架构师 Java
JVM进阶调优系列(6)一文详解JVM参数与大厂实战调优模板推荐
本文详述了JVM参数的分类及使用方法,包括标准参数、非标准参数和不稳定参数的定义及其应用场景。特别介绍了JVM调优中的关键参数,如堆内存、垃圾回收器和GC日志等配置,并提供了大厂生产环境中常用的调优模板,帮助开发者优化Java应用程序的性能。
|
1月前
|
存储 监控 算法
JVM调优深度剖析:内存模型、垃圾收集、工具与实战
【10月更文挑战第9天】在Java开发领域,Java虚拟机(JVM)的性能调优是构建高性能、高并发系统不可或缺的一部分。作为一名资深架构师,深入理解JVM的内存模型、垃圾收集机制、调优工具及其实现原理,对于提升系统的整体性能和稳定性至关重要。本文将深入探讨这些内容,并提供针对单机几十万并发系统的JVM调优策略和Java代码示例。
49 2
|
1月前
|
存储 监控 算法
G1 垃圾回收器:底层原理与调优过程
【10月更文挑战第9天】G1(Garbage-First)垃圾回收器是Java虚拟机(JVM)中一款面向服务端应用的垃圾收集器,它在JDK 1.7中引入,并从JDK 9开始成为默认的垃圾回收器。G1的设计目标是在有限的停顿时间内,尽可能地提高系统的吞吐量,特别是在多CPU和大内存的场景下表现出色。
56 3
|
1月前
|
算法 Java 开发者
Java中的垃圾回收机制:从原理到实践
Java的垃圾回收机制(Garbage Collection, GC)是其语言设计中的一大亮点,它为开发者提供了自动内存管理的功能,大大减少了内存泄漏和指针错误等问题。本文将深入探讨Java GC的工作原理、不同垃圾收集器的种类及它们各自的优缺点,并结合实际案例展示如何调优Java应用的垃圾回收性能,旨在帮助读者更好地理解和有效利用Java的这一特性。
|
30天前
|
算法 JavaScript 前端开发
垃圾回收算法的原理
【10月更文挑战第13天】垃圾回收算法的原理
23 0
|
1月前
|
算法 Java
JVM进阶调优系列(3)堆内存的对象什么时候被回收?
堆对象的生命周期是咋样的?什么时候被回收,回收前又如何流转?具体又是被如何回收?今天重点讲对象GC,看完这篇就全都明白了。
|
1月前
|
前端开发 Java 应用服务中间件
JVM进阶调优系列(1)类加载器原理一文讲透
本文详细介绍了JVM类加载机制。首先解释了类加载器的概念及其工作原理,接着阐述了四种类型的类加载器:启动类加载器、扩展类加载器、应用类加载器及用户自定义类加载器。文中重点讲解了双亲委派机制,包括其优点和缺点,并探讨了打破这一机制的方法。最后,通过Tomcat的实际应用示例,展示了如何通过自定义类加载器打破双亲委派机制,实现应用间的隔离。