Java虚拟机 G1 GC 调优解析

本文涉及的产品
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
日志服务 SLS,月写入数据量 50GB 1个月
简介: 在上篇文章中,我们解析了 Java 虚拟机体系生态中基于 CMS GC 策略的调优场景及基本案例,具体链接为:Java虚拟机 CMS GC 调优解析。本文则重点介绍另一款当前比较流行的 GC 策略 - G1。

   在上篇文章中,我们解析了 Java 虚拟机体系生态中基于 CMS GC 策略的调优场景及基本案例,具体链接为:Java虚拟机 CMS GC 调优解析。本文则重点介绍另一款当前比较流行的 GC 策略 - G1。

   依据官方 Java 虚拟机的规划,自 Java 9 开始,在实际的生产环境中不再建议使用基于 ConcurrentMarkSweep(CMS)垃圾收集器。根据 JEP-291,已做出此决定以减轻GC 代码库的维护负担并加速新开发。毕竟,Java 9 之后,G1 GC 已成为默认的 GC 算法。(当然,基于不同的环境,Z 垃圾收集器-ZGC 、Shenandoah GC 亦逐渐开始成为主流算法)因此,我们可以根据实际业务场景考虑将我们的应用程序移至该算法。它可能提供比 CMS GC 算法更优的性能特征。由于其参数相对较少,因此调整起来要容易得多。此外,G1 同时也提供了一些选项以从内存中消除重复的字符串,从而可以帮助我们应用减少总体内存占用。

   其实,在某些场景下,基于 CMS GC 的性能有的时候往往会比 G1 要高、更优,因此,只有基于实际的性能验证,在官方所约束的环境下,才能决定采用哪种符合业务场景的 GC 策略。由于在更高版本的 Java 版本中已弃用 CMS,故此我们才迫不得已需要将其移植至另一种类型的 GC 策略。

   那么,如果我们在生产环境中基于 Java 9 后续的版本(以 11 为例),显性定义 CMS GC 策略,即关键字“ -XX:+ UseConcMarkSweepGC ”,将会出现何种异常呢?我们先来看个简要的场景,具体如下:


[administrator@JavaLangOutOfMemory cpu ]% java -version
java version "11.0.10" 2021-01-19 LTS
Java(TM) SE Runtime Environment 18.9 (build 11.0.10+8-LTS-162)
Java HotSpot(TM) 64-Bit Server VM 18.9 (build 11.0.10+8-LTS-162, mixed mode)

   基于上述参数,我们定义了 Java 11 版本环境,接下来,通过运行一个简单的 Java 应用来对比验证 CMS GC 在 Java 9 后续版本环境中的表现情况,具体如下所示:


[administrator@JavaLangOutOfMemory cpu ]% cat startup-cms.sh
#!/bin/bash
java -Xms512M -Xmx512M -XX:+UseConcMarkSweepGC -verbose:gc -XX:+PrintCommandLineFlags -XX:+ExplicitGCInvokesConcurrent -Xlog:gc*=debug:file=/Users/administrator/cpu/admin_gc_log/admin_gc.log:utctime,level,tags:filecount=50,filesize=100M -Duser.timezone=GMT+11 -jar /Users/administrator/cpu/devopsDemo.jar PROBLEM_IO
[administrator@JavaLangOutOfMemory cpu ]% ./startup-cms.sh 
Java HotSpot(TM) 64-Bit Server VM warning: Option UseConcMarkSweepGC was deprecated in version 9.0 and will likely be removed in a future release.
-XX:+ExplicitGCInvokesConcurrent -XX:InitialHeapSize=536870912 -XX:MaxHeapSize=536870912 -XX:MaxNewSize=178958336 -XX:MaxTenuringThreshold=6 -XX:NewSize=178958336 -XX:OldSize=357912576 -XX:+PrintCommandLineFlags -XX:ReservedCodeCacheSize=251658240 -XX:+SegmentedCodeCache -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseConcMarkSweepGC 
[0.043s][info][gc] Using Concurrent Mark Sweep
Application started!
Starting to write to fileIO-1.txt
Starting to write to fileIO-2.txt
Starting to write to fileIO-3.txt
Starting to write to fileIO-4.txt
Starting to write to fileIO-5.txt
[1.295s][info][gc] GC(0) Pause Young (Allocation Failure) 136M->0M(494M) 7.478ms
Read & write 1000 times to fileIO-5.txt
Read & write 1000 times to fileIO-1.txt
Read & write 1000 times to fileIO-3.txt
Read & write 1000 times to fileIO-4.txt
Read & write 1000 times to fileIO-2.txt
[2.321s][info][gc] GC(1) Pause Young (Allocation Failure) 137M->0M(494M) 3.365ms
Read & write 1000 times to fileIO-4.txt
Read & write 1000 times to fileIO-1.txt
Read & write 1000 times to fileIO-5.txt
Read & write 1000 times to fileIO-3.txt
Read & write 1000 times to fileIO-2.txt
[3.089s][info][gc] GC(2) Pause Young (Allocation Failure) 137M->1M(494M) 3.114ms
Read & write 1000 times to fileIO-5.txt
Read & write 1000 times to fileIO-1.txt
Read & write 1000 times to fileIO-4.txt
^C%

   基于上述的执行情况,我们可以看到,如果使用 -XX:+UseConcMarkSweepGC 策略启动应用程序 ,将激活 CMS GC 事件机制,应用程序也能够运行起来,此时将看到以下警告消息:“ Java HotSpot(TM) 64-Bit Server VM warning: Option UseConcMarkSweepGC was deprecated in version 9.0 and will likely be removed in a future release ” 。关于 CMS GC 的相关内容,可参考之前文章:CMS GC已成过去式。接下来,我们进入本文的主题 Garbage First GC 。

Heap

  其实,针对 G1 GC 优化,在之前的文章中有所涉及,大家有兴趣可以查阅,链接为:G1 GC简单优化技巧。本文将会从基础的架构原理角度,基于具体的场景进行阐述,以便大家能够更加深入去理解整个 G1 GC 技术体系。从本质上讲,无论是基于早期的Serial、Parallel、CMS、G1 ,还是即将或已落地的 ZGC、Shenandoah GC,它们之间在某种特定的意义角度还是有相互关联的。

   基于对象的生命周期发展,G1 与串行,并行和 CMS GC 没什么不同,都是基于分代垃圾收集器概念,通俗意义上来讲,在大多数对象都死于年轻的前提下,将堆分成若干代。在年轻代中处理(清理)对象比转移到老年代并在那里清理要更加简洁、有效。

   基于堆的规划层面,G1 与传统的分代垃圾收集器还是有所差异,在 G1 之前,堆被定义为连续的区域,具体如下图所示:

    基于上述参考示意图,我们可以看到堆空间已被定义为不同的代(Generation),即年轻代(由 Eden Space 和 Survivor 等区域组成)和老年代,同时,此空间也是连续的。

   相对比传统的而言,G1 GC 在堆空间分配上有很大的不同。G1 将堆划分为多个(通常为2048个)较小的 Region(堆区域,Region 的大小可以通过 G1 HeapRegionSize 参数进行设置,其必须是2的幂,范围允许为 1Mb 到 32 Mb。JVM 会基于堆内存的初始值和最大值的平均数计算分区的尺寸,平均的堆尺寸会分出约 2000 个 Region。分区大小一旦设置,则启动之后不会再变化。因此,堆大小直接改变了区域大小。每个区域都可以分配为伊甸园,幸存者或旧区域。分配给 Eden,Survivor 或 Old 的区域数量是灵活的,由 GC 在运行时确定。具体如下图所示:

   注:Humongous regions(巨型对象区域)、Free resgions(未分配区域,也会叫做可用分区)暂未在上述图中进行标注。

GC 阶段

   通常,在基于 G1 GC 策略场景中,主要存在3个核心的垃圾收集策略。(严格声明:JDK 10 之前的 G1中的 GC 只有 Young GC 和 Mixed GC。FullGC处理会交给单线程的 Serial Old 垃圾收集器。)


   1、Minior GC - 年轻代垃圾收集

   也称为“ Young GC ”,一些年轻的 GC 在 Eden Survivor 之间进行移动,并最终将其移到 Old 空间。即在我们 New 一个对象(非巨型对象)时,并对其进行空间分配,当所有 Eden Region 使用达到最大阀值并且无法申请足够内存时,会触发一次 Young GC。每次 Young GC 会回收所有 Eden 以及 Survivor 区,并且将存活对象复制到 Old 区以及另一部分的 Survivor 区。到 Old 区的标准就是在 PLAB 中得到的计算结果。因为Young GC 会进行根扫描,所以会进行 Stop the World。


   2、Mixed GC - 混合垃圾收集

  此阶段涉及一个或多个混合收集,即新一代以及垃圾量最大的许多旧区域(可配置)。在混合收集结束时,G1 确定是否需要另一个混合收集以达到其阈值(可配置)。此后,该循环再次从另一个年轻的 GC 阶段开始。Mixed GC 是 G1 GC 特有的,跟 Full GC 不同的是 Mixed GC 只回收部分老年代的 Region。通常,基于全局角度,Mixed GC 一般会发生在一次 Young GC 后面,为了提高效率,Mixed GC 会复用Young GC 的全局的根扫描结果,毕竟 Stop the World 过程是不可或缺的,从整体层面上来说其缩短了暂停时间。

   3、Full GC - 全局垃圾收集

   与其他 GC 一样,这是最终的选择。如果应用程序在收集活动信息时内存不足,则可能导致 Stop-the-World 的 Full GC,即年轻代和老年代。具体来讲,G1 在对象复制/转移失败或者没法分配足够内存(比如巨型对象没有足够的连续分区分配)时,会触发Full GC。Full GC 使用的是 Stop the World 的单线程的 Serial Old 模式,所以一旦触发Full GC 则会 STW 应用线程,并且执行效率很慢。其实,从本质上讲,JDK 8 版本的 G1 是不提供 Full GC 事件的处理。G1 GC 和其他分代垃圾收集器的主要目标之一:避免使用昂贵的 Full GC。

GC 调优

    与传统的 CMS 相比,G1 GC 可用的 JVM 参数选项明显要少得多( 官方所定义:CMS 72、G1 26、ZGC 8),主要目的是为了减少使用。通常,基于 G1 GC 的 JVM 的基本策略主要取决于2点:堆大小和暂停时间,然后让 JVM 动态修改所需的设置以尝试满足暂停时间目标。如果未达到性能目标,需要考虑基于 GC 监视和日志分析的其他选项。这是一个反复的过程,重要的是要确保有足够的时间和资源分配给此关键任务。

   1、Heap 大小

   在 JDK 11 之前,若我们在生产环境中使用基于 G1 GC 策略,需要显性定义参数: -XX:+ UseG1GC ,通过设置此关键字,才能使得策略场景生效。毕竟,在 Java 8上,默认 GC 是 CMS GC,而在 Java 11 上,默认 GC 才是 G1 GC。这也意味着,在升级 Java 版本时,除非明确设置了 GC 策略类型,否则其仍然依据厂商所定义的策略执行。除此之外,针对堆的内存分配,建议将 -Xms 和 -Xmx 大小显式设置为相同的值,以避免在应用程序生命周期中堆的动态收缩和增长。具体可以使用以下 JVM 参数选项执行此操作:



-XX:InitialHeapSize(最小Java堆大小)---等同于 -Xms
-XX:MaxHeapSize(最大Java堆大小)---等同于 -Xmx

   2、GC Pause 目标

   通常,在基于 G1 GC 策略场景中,建议设置暂停时间目标,并让 GC 能够根据实际需要更新堆。除此之外,谨记:除非需要,否则不要设置年轻代(-XX:NewSize -XX:MaxNewSize 或 -Xmn)的大小。否则,当我们显性手工设置了其大小,就意味着放弃了 G1 的自动调优。要设置暂停时间目标,我们需要设置以下 JVM 参数选项(默认值为200),具体如下所示:


-XX:MaxGCPauseMillis=500

   基于上述参数,然后对其进行性能验证,以检测当前设置是否满足预期的性能目标。毕竟,对这个参数的调优是一个持续的过程,逐步调整到最佳状态。暂停时间只是一个目标,在实际的业务场景中并不能总是得到满足。  

   3、GC Logging

  针对 GC Log 的使用,与早期的 CMS 并无差异,只不过,从 Java 9 开始,统一的JVM 日志记录已经取代了旧的日志记录选项。Java 8 的日志记录选项不能用于 Java11。具体如下:


-XX:+UseG1GC -XX:InitialHeapSize=2048M -XX:MaxHeapSize=2048M -XX:MaxGCPauseMillis=500 -XX:+DisableExplicitGC -XX:+UseStringDeduplication -XX:+ParallelRefProcEnabled -XX:MaxMetaspaceSize=256m -XX:MaxTenuringThreshold=1 -Xlog:gc=debug:file=/data/logs/admin-web-gc.log:time,uptime,level,tags:filecount=5,filesize=100m

   4、其他辅助性参数

    针对辅助性优化,主要针对 Mixed GC 的调整,具体涉及以下参数:


-XX:InitiatingHeapOccupancyPercent -XX:G1MixedGCLiveThresholdPercent -XX:G1MixedGCCountTarger

   针对其他方面的优化,可参考之前的文章,具体链接为:G1 GC简单优化技巧


   综上所述,本文所解析的内容主要基于 JDK 8 版本环境,在 Jdk10 及以上版本的 G1 GC 会有更多的优化。Full CG 方面,将提供并发标记的 Full GC 方案:Parallelize Mark-Sweep-Compact。Card Table 的扫描也会得到加速。同时,也对 RSet 进行更新优化,目前的 RSet 会存储在所有的分区里,新版本的 RSet 只需要在 CSet 中,并且是在 Remark 到 Clean 阶段之间并发构建 RSet。这项优化会增加整个并发标记的周期,但是缩减了很多 RSet 的占用空间。另外,对于 PauseTime 会有更精准的处理,在 Mixed GC 的对象拷贝阶段,提供了可放弃拷贝的(Abortable)选项。Mixed GC 会计算下一个 Region 的对象拷贝,如果可能会超过预期的 Pause Time,则会放弃这次拷贝。

   至此,关于 Java虚拟机 G1 GC 调优解析相关内容本文到此为止,大家有什么疑问、想法及建议,欢迎留言沟通。

相关文章
|
10天前
|
存储 监控 算法
jvm-性能调优(二)
jvm-性能调优(二)
|
21天前
|
设计模式 Java 关系型数据库
【Java笔记+踩坑汇总】Java基础+JavaWeb+SSM+SpringBoot+SpringCloud+瑞吉外卖/谷粒商城/学成在线+设计模式+面试题汇总+性能调优/架构设计+源码解析
本文是“Java学习路线”专栏的导航文章,目标是为Java初学者和初中高级工程师提供一套完整的Java学习路线。
176 37
|
15天前
|
缓存 Java 应用服务中间件
Java虚拟线程探究与性能解析
本文主要介绍了阿里云在Java-虚拟-线程任务中的新进展和技术细节。
|
12天前
|
设计模式 安全 Java
Java 编程中的设计模式:单例模式的深度解析
【9月更文挑战第22天】在Java的世界里,单例模式就像是一位老练的舞者,轻盈地穿梭在对象创建的舞台上。它确保了一个类仅有一个实例,并提供全局访问点。这不仅仅是代码优雅的体现,更是资源管理的高手。我们将一起探索单例模式的奥秘,从基础实现到高级应用,再到它与现代Java版本的舞蹈,让我们揭开单例模式的面纱,一探究竟。
23 11
|
6天前
|
存储 算法 Java
深入解析 Java 虚拟机:内存区域、类加载与垃圾回收机制
本文介绍了 JVM 的内存区域划分、类加载过程及垃圾回收机制。内存区域包括程序计数器、堆、栈和元数据区,每个区域存储不同类型的数据。类加载过程涉及加载、验证、准备、解析和初始化五个步骤。垃圾回收机制主要在堆内存进行,通过可达性分析识别垃圾对象,并采用标记-清除、复制和标记-整理等算法进行回收。此外,还介绍了 CMS 和 G1 等垃圾回收器的特点。
18 0
深入解析 Java 虚拟机:内存区域、类加载与垃圾回收机制
|
12天前
|
缓存 负载均衡 Dubbo
Dubbo技术深度解析及其在Java中的实战应用
Dubbo是一款由阿里巴巴开源的高性能、轻量级的Java分布式服务框架,它致力于提供高性能和透明化的RPC远程服务调用方案,以及SOA服务治理方案。
39 6
|
9天前
|
监控 算法 Java
深入解析Java中的垃圾回收机制
本文旨在全面解析Java的垃圾回收机制,探讨其工作原理、常见算法以及在实际开发中的应用。通过对这一重要主题的深入分析,希望帮助读者更好地理解Java虚拟机(JVM)如何管理内存,从而编写出更高效、稳定的Java应用程序。
|
9天前
|
Java 开发者
Java中的异常处理机制深度解析
在Java编程中,异常处理是保证程序稳定性和健壮性的重要手段。本文将深入探讨Java的异常处理机制,包括异常的分类、捕获与处理、自定义异常以及一些最佳实践。通过详细讲解和代码示例,帮助读者更好地理解和应用这一机制,提升代码质量。
12 1
|
11天前
|
Kubernetes Java 编译器
解锁极致性能:Quarkus如何让JVM应用调优变得前所未有的简单与高效!
Quarkus是一款专为GraalVM和OpenJDK设计的Kubernetes Native Java框架,采用AOT编译技术将Java应用转化为本地代码,大幅提升启动速度与运行效率。它简化了性能调优流程,如自动优化垃圾回收、类加载、内存管理及线程管理等,使开发者无需深入理解JVM细节即可轻松提升应用性能。与传统JVM应用相比,Quarkus显著降低了性能调优的复杂度。
44 2
|
13天前
|
监控 算法 Java
深入理解Java中的垃圾回收机制在Java编程中,垃圾回收(Garbage Collection, GC)是一个核心概念,它自动管理内存,帮助开发者避免内存泄漏和溢出问题。本文将探讨Java中的垃圾回收机制,包括其基本原理、不同类型的垃圾收集器以及如何调优垃圾回收性能。通过深入浅出的方式,让读者对Java的垃圾回收有一个全面的认识。
本文详细介绍了Java中的垃圾回收机制,从基本原理到不同类型垃圾收集器的工作原理,再到实际调优策略。通过通俗易懂的语言和条理清晰的解释,帮助读者更好地理解和应用Java的垃圾回收技术,从而编写出更高效、稳定的Java应用程序。

推荐镜像

更多
下一篇
无影云桌面