首页 最新 热门 推荐

  • 首页
  • 最新
  • 热门
  • 推荐

Java Hotspot G1 GC的理解总结

  • 25-03-02 10:41
  • 2583
  • 7932
blog.csdn.net

目录

一、基本背景概述

内存划分简介

二、重点基础概念介绍

(一)Region(分区)

(二)Card(卡片)

(三)CSet(待回收Region集合)

(四)RSet(引用索引集合)

(五)SATB(snapshot-at-the-beginning)

(六) Marking bitmap(位图)和TAMS

三、G1收集器垃圾回收过程

G1回收过程一:年轻代 GC

G1回收过程二:并发标记过程

G1回收过程三:混合回收过程

G1 回收可选的过程四:Full GC

四、适用场景分析

五、应用建议

六、常用参数展示

参考文献、书籍及链接


干货分享,感谢您的阅读!

一、基本背景概述

Garbage-First (G1) 收集器是一种服务器式垃圾收集器,针对具有大内存的多处理器机器。它尽可能地满足目标暂停时间,同时兼顾高吞吐量。全称Garbage-First Garbage Collector,通过参数来启用-XX:+UseG1GC,在JDK 7u4版本发行时被正式推出,在JDK 9中被提议设置为默认垃圾收集器(JEP 248)。官网描述如下:

The Garbage-First (G1) collector is a server-style garbage collector, targeted for multi-processor machines with large memories. It meets garbage collection (GC) pause time goals with a high probability, while achieving high throughput. The G1 garbage collector is fully supported in Oracle JDK 7 update 4 and later releases. The G1 collector is designed for applications that:

> * Can operate concurrently with applications threads like the CMS collector.

> * Compact free space without lengthy GC induced pause times.

> * Need more predictable GC pause durations.

> * Do not want to sacrifice a lot of throughput performance.

> * Do not require a much larger Java heap.

从官网的描述中,它是专门针对以下应用场景设计的: 

  • 像CMS收集器一样,能与应用程序线程并发执行。
  • 整理空闲空间更快。 
  • 需要GC停顿时间更好预测。
  • 不希望牺牲大量的吞吐性能。
  • 不需要更大的Java Heap。

内存划分简介

之前的垃圾收集器(serial, parallel, CMS)都将堆结构划分为三个部分:新生代、老年代和固定大小的永久代。这些收集器都只针对其中一个部分进行垃圾收集。

鉴于CMS 的一些不足,比如: STW时长与内存大小显著正相关(超大堆停顿时间长)、STW时长不可控、老年代内存碎片化,G1 就横空出世了。设计初衷是为了尽量缩短处理超大堆时产生的停顿。可以设置-XX:MaxGCPauseMillis,控制GC的停顿时间。G1在回收的时候将对象从一个小堆区复制到另一个小堆区,这意味着G1在回收垃圾的时候同时完成了堆的部分内存压缩,相对于CMS的优势而言就是内存碎片的产生率大大降低。

G1 对于 heap 区的内存划思路很新颖,将 heap 内存区,划分为一个个大小相等(1-32M,2 的 n 次方)、内存连续的 Region 区域,每个 region 都对应 Eden、Survivor 、Old、Humongous 四种角色之一,所以内存还是通过分代收集的,但是 region 与 region 之间不要求连续。

在上图中,标明H代表Humongous,是Region存储的巨大对象(humongous object,H-obj),即大小大于等于region一半的对象。H-obj有如下几个特征:

  1. H-obj直接分配到了old gen,防止了反复拷贝移动。
  2. H-obj在global concurrent marking阶段的cleanup 和 full gc阶段回收。
  3. 在分配H-obj之前先检查是否超过 initiating heap occupancy percent和the marking threshold, 如果超过的话,就启动global concurrent marking,为的是提早回收,防止 evacuation failures 和 full gc。

G1跟踪各个Region里面的垃圾堆积的价值大小(回收所获得的空间以及回收所需时间的经验值),维护一个优先列表,根据允许收集的时间,优先回收价值最大的Region。这种方式优先清除高价值的垃圾,所以称为:垃圾优先。

二、重点基础概念介绍

(一)Region(分区)

G1 将 heap 内存区,划分为一个个大小相等(通过参数-XX:G1HeapRegionSize=x,x=1-32M,2 的 n 次方)、内存连续的 Region 区域,每个 region 都对应 Eden、Survivor 、Old、Humongous 四种角色之一,所以内存还是通过分代收集的,但是 region 与 region 之间不要求连续。具体图示在上面已经展示过。

(二)Card(卡片)

在每个Region(分区)内部又被分成了若干个大小为512 Byte卡片(Card),标识堆内存最小可用粒度所有分区的卡片将会记录在全局卡片表(Global Card Table)中,分配的对象会占用物理上连续的若干个卡片,当查找对分区内对象的引用时便可通过记录卡片来查找该引用对象(见RSet)。每次对内存的回收,都是对指定分区的卡片进行处理。

(三)CSet(待回收Region集合)

CSet记录的是GC等待收集的Region的集合,CSet里的Region可以是任意代的。总体上CSet消耗的内存小于 1%,G1垃圾回收器的软实时的特性就是通过CSet的选择来实现的。

 对应于算法的两种模式fully-young generational mode和partially-young mode:

fully-young generational mode:该模式下CSet将只包含young的Region,调整young的Region的数量来匹配软实时的目标;

partially-young mode:该模式会选择所有的young region,并且选择一部分的old region。old region的选择将依据在Marking cycle phase中对存活对象的计数。G1选择存活对象最少的Region进行回收。

(四)RSet(引用索引集合)

RSet背景:由于 region 与 region 之间并不要求连续,而使用 G1 的场景通常是大内存,比如 64G 甚至更大,为了提高扫描根对象和标记的效率。

RSet所占用的JVM内存小于总大小的5%。数据结构为hashtable,key为外部分区(Region)的起始地址,value为引用对象所在的卡片(Card)的索引。每个Region都有一个RSet,RSet记录了其他Region中的对象引用本Region中对象的关系。内部类似一个反向指针,记录引用分区内对象的卡片索引。当要回收该分区时,通过扫描分区的RSet,来确定引用本分区内的对象是否存活,进而确定本分区内的对象存活情况。

RSet对young/mixed GC的性能非常有帮助(不必扫描所有老年代Region,只需要扫描RSet),随之而来的就是RSet的维护成本:写屏障(write barrier)与并发优化线程,写栅栏交由应用线程(mutator)执行,其原理为在所有指针修改操作之后插入写栅栏代码(可以理解为指令级AOP)。

(五)SATB(snapshot-at-the-beginning)

SATB是维持并发GC的正确性的一个手段,G1GC的并发理论基础就是SATB,SATB是由Taiichi Yuasa为增量式标记清除垃圾收集器设计的一个标记算法。

SATB算法创建了一个对象图,它是堆的一个逻辑“快照”。标记数据结构包括了两个位图:previous位图和next位图。previous位图保存了最近一次完成的标记信息,并发标记周期会创建并更新next位图,随着时间的推移,previous位图会越来越过时,最终在并发标记周期结束的时候,next位图会将previous位图覆盖掉。

(六) Marking bitmap(位图)和TAMS

Marking bitmap是一种数据结构,其中的每一个bit代表的是一个可用于分配给对象的起始地址。

其中addrN代表的是一个对象的起始地址。绿色的块代表的是在该起始地址处的对象是存活对象,而其余白色的块则代表了垃圾对象。
G1使用了两个bitmap,一个叫做previous bitmap,另外一个叫做next bitmap。previous bitmap记录的是上一次的标记阶段完成之后的构造的bitmap;next bitmap则是当前正在标记阶段正在构造的bitmap。在当前标记阶段结束之后,当前标记的next bitmap就变成了下一次标记阶段的previous bitmap。
TAMS(top at mark start)变量,是一对用于区分在标记阶段新分配对象的变量,分别被称为previous TAMS和next TAMS。在previous TAMS和next TAMS之间的对象则是本次标记阶段时候新分配的对象。

白色region代表的是空闲空间,绿色region代表是存活对象,橙色region代表的在此次标记阶段新分配的对象。注意的是,在橙色区域的对象,并不能确保它们都事实上是存活的。

三、G1收集器垃圾回收过程

G1 GC的垃圾回收过程主要包括如下三个环节:

  1. 年轻代GC(Young GC)
  2. 老年代并发标记过程(Concurrent Marking)
  3. 混合回收(Mixed GC)

如果需要,单线程、独占式、高强度的Full GC还是继续存在的。它针对GC的评估失败提供了一种失败保护机制,即强力回收。

具体说明:

应用程序分配内存,当年轻代的Eden区用尽时开始年轻代回收过程;G1的年轻代收集阶段是一个并行的独占式收集器。在年轻代回收期,G1 GC暂停所有应用程序线程,启动多线程执行年轻代回收。然后从年轻代区间移动存活对象到Survivor区间或者老年区间,也有可能是两个区间都会涉及。

当堆内存使用达到一定值(默认45%)时,开始老年代并发标记过程。标记完成马上开始混合回收过程。对于一个混合回收期,G1 GC从老年区间移动存活对象到空闲区间,这些空闲区间也就成为了老年代的一部分。和年轻代不同,老年代的G1回收器和其他GC不同,G1的老年代回收器不需要整个老年代被回收,一次只需要扫描/回收一小部分老年代的Region就可以了。同时,这个老年代Region是和年轻代一起被回收的。

举例:一个Web服务器,Java进程最大堆内存为4G,每分钟响应1500个请求,每45秒钟会新分配大约2G的内存。G1会每45秒钟进行一次年轻代回收,每31个小时整个堆的使用率会达到45%,会开始老年代并发标记过程,标记完成后开始四到五次的混合回收。

G1回收过程一:年轻代 GC

JVM启动时,G1先准备好Eden区,程序在运行过程中不断创建对象到Eden区,当Eden空间耗尽时,G1会启动一次年轻代垃圾回收过程,年轻代回收只回收Eden区和Survivor区。

YGC时,首先G1停止应用程序的执行(Stop-The-World),G1创建回收集(Collection Set),回收集是指需要被回收的内存分段的集合,年轻代回收过程的回收集包含年轻代Eden区和Survivor区所有的内存分段。如图,回收完E和S区,剩余存活的对象会复制到新的S区,S区达到一定的阈值可以晋升为O区。

 细致过程:

  • 第一阶段:扫描根,GC Roots根引用连同RSet记录的外部引用作为扫描存活对象的入口。
  • 第二阶段:更新RSetG1回收过程二:并发标记过程
  • 第三阶段:处理RSet,识别被老年代对象指向的Eden中的对象,这些被指向的Eden中的对象被认为是存活的对象。
  • 第四阶段:复制对象。遍历对象树,Eden区内存段中存活的对象会被复制到Survivor区中空的内存分段,Survivor区内存段中存活的对象
  • 如果年龄未达阈值,年龄会加1,达到阀值会被会被复制到Old区中空的内存分段。
  • 如果Survivor空间不够,Eden空间的部分数据会直接晋升到老年代空间。
  • 第五阶段:处理引用,处理Soft,Weak,Phantom,Final,JNI Weak 等引用。最终Eden空间的数据为空,GC停止工作,而目标内存中的对象都是连续存储的,没有碎片,所以复制过程可以达到内存整理的效果,减少碎片。

G1回收过程二:并发标记过程

基本步骤如下展示:

初始标记阶段

标记从根节点直接可达的对象。这个阶段是STW的,并且会触发一次年轻代GC。正是由于该阶段时STW的,所以我们只扫描根节点可达的对象,以节省时间。

根区域扫描(Root Region Scanning):G1 GC扫描Survivor区直接可达的老年代区域对象,并标记被引用的对象。这一过程必须在Young GC之前完成,因为Young GC会使用复制算法对Survivor区进行GC。

并发标记(Concurrent Marking)

在整个堆中进行并发标记(和应用程序并发执行),此过程可能被Young GC中断。在并发标记阶段,若发现区域对象中的所有对象都是垃圾,那这个区域会被立即回收。同时,并发标记过程中,会计算每个区域的对象活性(区域中存活对象的比例)。

再次标记(Remark)

由于应用程序持续进行,需要修正上一次的标记结果。是STW的。G1中采用了比CMS更快的原始快照算法:Snapshot-At-The-Beginning(SATB)。

独占清理(cleanup,STW)

计算各个区域的存活对象和GC回收比例,并进行排序,识别可以混合回收的区域。为下阶段做铺垫。这个阶段并不会实际上去做垃圾的收集

并发清理阶段

识别并清理完全空闲的区域。

G1回收过程三:混合回收过程

当越来越多的对象晋升到老年代old region时,为了避免堆内存被耗尽,虚拟机会触发一个混合的垃圾收集器,即mixed gc,该算法并不是一个old gc,除了回收整个young region,还会回收一部分的old region,这里需要注意:是一部分老年代,而不是全部老年代,可以选择哪些old region进行收集,从而可以对垃圾回收的耗时时间进行控制。

-XX:G1MixedGCCountTarget:默认是8,意味着混合回收GC数目目标为8个,每次混合回收暂停的最小老年代Region数目的计算公式:每一次混合回收暂停的最小老年代区间数目=混合回收循环确认的候选老年代区间总数 / G1MixedGCCountTarget。

时机触发:XX:InitiatingHeapOccupancyPercent=45,当老年代大小占整个堆大小百分比达到该阈值时,会触发一次mixed gc

MixedGC的回收过程如下:

扩展注意事项:

并发标记结束以后,老年代中百分百为垃圾的内存分段被回收了,部分为垃圾的内存分段被计算了出来。默认情况下,这些老年代的内存分段会分8次(可以通过-XX:G1MixedGCCountTarget设置)被回收。

混合回收的回收集(Collection Set)包括八分之一的老年代内存分段,混合回收的算法和年轻代回收的算法完全一样,只是回收集多了老年代的内存分段。具体过程请参考年轻代回收过程。

由于老年代中的内存分段默认分8次回收,G1会优先回收垃圾多的内存分段。垃圾占内存分段比例越高的,越会被先回收。并且有一个阈值会决定内存分段是否被回收。XX:G1MixedGCLiveThresholdPercent,默认为65%,意思是垃圾占内存分段比例要达到65%才会被回收。如果垃圾占比太低,意味着存活的对象占比高,在复制的时候会花费更多的时间。

混合回收并不一定要进行8次。有一个阈值-XX:G1HeapWastePercent,默认值为10%,意思是允许整个堆内存中有10%的空间被浪费,意味着如果发现可以回收的垃圾占堆内存的比例低于10%,则不再进行混合回收。因为GC会花费很多的时间但是回收到的内存却很少。

G1 回收可选的过程四:Full GC

G1的初衷就是要避免Full GC的出现。但是如果上述方式不能正常工作,G1会停止应用程序的执行(Stop-The-World),使用单线程的内存回收算法进行垃圾回收,性能会非常差,应用程序停顿时间会很长。

要避免Full GC的发生,一旦发生Full GC,需要对JVM参数进行调整。不论是年轻代还是老年代,G1都采用复制算法进行收集,因此需要空闲分区才能正常进行。若G1在执行的过程中,没有足够的空闲分区,则会导致full GC的发生。可能的情况为:

1)to space exhausted:Young GC过程中Survivor 和 Old 区无法找到新的空闲分区,导致疏散失败

2)promotion failure:Mixed GC实在无法跟上程序分配内存的速度,导致对象晋升失败

3)concurrent mode failure:Mixed GC如果在标记结束前,老年代被填满,G1 会放弃标记,导致并发模式失败

4)在分配巨型对象时无法找到合适的空闲分区,导致大对象分配失败

5)程序显式调用System.gc()。这里有个例外,若加上参数-XX:+ExplicitGCInvokesConcurrent,则G1会强行启动一次全局并发标记。很多NIO框架都设置此参数(为了回收堆外内存),从而避免引发full GC而导致性能下降

四、适用场景分析

基于其基本的优势,如并行与并发兼备、分代收集、空间整合、可预测的停顿时间模型(即:软实时soft real-time)等优势,其主要适用场景如下

  • 面向服务端应用,针对具有大内存、多处理器的机器(在堆大小约6GB或更大时,可预测的暂停时间可以低于0.5秒)。(在普通大小的堆里表现并不惊喜)
  • 最主要的应用是需要低GC延迟,并具有大堆的应用程序提供解决方案;
  • 用来替换掉JDK1.5中的CMS收集器,在下面的情况时,使用G1可能比CMS好:
  • 超过50%的Java堆被活动数据占用;
  • 对象分配频率或年代提升频率变化很大;
  • GC停顿时间过长(长于0.5至1秒)
  • HotSpot垃圾收集器里,除了G1以外,其他的垃圾收集器均使用内置的JVM线程执行GC的多线程操作,而G1 GC可以采用应用线程承担后台运行的GC工作,即当JVM的GC线程处理速度慢时,系统会调用应用程序线程帮助加速垃圾回收过程。

五、应用建议

介绍一些关于 G1 垃圾回收器优化的一般性建议:

  1. 调整区域大小:在使用 G1 垃圾回收器时,可以根据应用内存大小和分配情况调整各个区域的大小。例如,可以增加年轻代的大小,以减少对象晋升老年代的频率,同时调整老年代的大小,以充分利用多线程并发处理垃圾回收。
  2. 调整并发线程数:在进行 G1 垃圾回收时,可以根据机器配置和应用负载情况调整并发垃圾回收线程数。例如,可以增加并发垃圾回收线程数以加速垃圾回收速度和提高吞吐量。
  3. 设置目标停顿时间:在进行 G1 垃圾回收时,可以通过设置目标停顿时间来平衡垃圾回收速度和响应时间。例如,可以设置较短的目标停顿时间来保证应用的响应速度,但这可能会导致垃圾回收的效率降低。
  4. 分析 GC 日志:在进行 G1 垃圾回收优化时,可以通过分析 GC 日志来了解垃圾回收的实际情况,从而进行针对性的调优。例如,可以根据 GC 日志来确定哪些对象占用了大量内存,从而进行内存泄漏的排查和解决。

需要注意的是,G1 垃圾回收器的优化需要根据具体的应用场景和需求进行,不能一概而论。在进行 G1 垃圾回收器的优化时,需要结合实际情况进行参数调整和性能监控,以达到最优的性能和稳定性表现。

六、常用参数展示

选项/默认值说明
-XX:+UseG1GC使用 G1 (Garbage First) 垃圾收集器
-XX:MaxGCPauseMillis=n

设置最大GC停顿时间(GC pause time)指标(target). 这是一个软性指标(soft goal),这是一个大概值,JVM 会尽可能的满足此值。(默认200ms)。

代替使用平均响应时间(ART)做为指标,考虑设置值将会符合这个时间的90%或者更高比例。这意味着90%的用户发出一个请求将不会经历高于这个目标的时间。暂停时间只是一个目标,不保证总是能够达到。

-XX:InitiatingHeapOccupancyPercent=n设置触发标记周期的 Java 堆占用率阈值。启动并发GC周期时的堆内存占用百分比. G1之类的垃圾收集器用它来触发并发GC周期,基于整个堆的使用率,而不只是某一代内存的使用比. 值为 0 则表示"一直执行GC循环". 默认占用率是整个 Java 堆的 45%,默认值为 45.
-XX:NewRatio=n老年代与年轻代(old/new generation)的大小比例(Ratio). 默认值为 2.
-XX:SurvivorRatio=neden/survivor 空间大小的比例(Ratio). 默认值为 8.
-XX:GCTimeRatio吞吐量大小,0-100的整数(默认9),值为n则系统将花费不超过1/(1+n)的时间用于垃圾收集
-XX:MaxTenuringThreshold=n提升年老代的最大临界值(tenuring threshold). 默认值为 15.
-XX:ParallelGCThreads=n设置STW工作线程数的值,与使用的CPU的数量有关,最大值为8。如果CPU数量超过8个,则最多可以设置总CPU数量的 5/8。
-XX:ConcGCThreads=n设置并行标记线程数
-XX:G1ReservePercent=n设置预留空间的空闲百分比,以降低目标空间的溢出风险,默认为10%
-XX:G1HeapRegionSize=n使用G1时Java堆会被分为大小统一的的区(region)。此参数可以指定每个heap区的大小. 默认值将根据 heap size 算出最优解. 最小值为 1Mb, 最大值为 32Mb.
-XX:G1NewSizePercent=n设置年轻代最小使用的空间比率,默认为Java堆内存的50%
-XX:G1MaxNewSizePercent=n设置年轻代最大使用的空间比率,默认为Java堆内存的60%
XX:G1HeapWastePercent堆废物百分比(默认5%)
-XX:G1MixedGCCountTarget参数混合周期的最大总次数(默认8)
-XX:G1PrintRegionLivenessInfo默认值false, 在情理阶段的并发标记环节,输出堆中的所有 regions 的活跃度信息
-XX:G1PrintHeapRegions默认值false, G1 将输出那些 regions 被分配和回收的信息
-XX:+PrintSafepointStatistics输出具体的停顿原因
-XX:+PrintGCApplicationStoppedTime停顿时间输出到GC日志中
-XX:-UseBiasedLocking取消偏向锁
-XX:+UseGCLogFileRotation开启滚动日志输出,避免内存被浪费
-XX:+PerfDisableSharedMem关闭 jstat 性能统计输出特性,使用 jmx 代替
-XX:TargetSurvivorRatio:Survivor填充容量(默认50%)

参考文献、书籍及链接

1.Java Hotspot G1 GC的一些关键技术 - 美团技术团队

2.G1收集器与CMS收集器的对比与实战 - Chris Blog - Java博文专集

3.一文看懂JVM内存布局及GC原理_技术管理_杨俊明_InfoQ精选文章

4.《深入理解Java虚拟机》

5.Getting Started with the G1 Garbage Collector

6.https://github.com/youthlql/JavaYouth/blob/main/docs/Java/JVM/JVM%E7%B3%BB%E5%88%97-%E7%AC%AC12%E7%AB%A0-%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6%E5%99%A8.mdicon-default.png?t=N7T8https://github.com/youthlql/JavaYouth/blob/main/docs/Java/JVM/JVM%E7%B3%BB%E5%88%97-%E7%AC%AC12%E7%AB%A0-%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6%E5%99%A8.md

7.尚硅谷深入理解JVM课程

文章知识点与官方知识档案匹配,可进一步学习相关知识
Java技能树首页概览148946 人正在系统学习中
注:本文转载自blog.csdn.net的张彦峰ZYF的文章"https://blog.csdn.net/xiaofeng10330111/article/details/106081590"。版权归原作者所有,此博客不拥有其著作权,亦不承担相应法律责任。如有侵权,请联系我们删除。
复制链接
复制链接
相关推荐
发表评论
登录后才能发表评论和回复 注册

/ 登录

评论记录:

未查询到任何数据!
回复评论:

分类栏目

后端 (14832) 前端 (14280) 移动开发 (3760) 编程语言 (3851) Java (3904) Python (3298) 人工智能 (10119) AIGC (2810) 大数据 (3499) 数据库 (3945) 数据结构与算法 (3757) 音视频 (2669) 云原生 (3145) 云平台 (2965) 前沿技术 (2993) 开源 (2160) 小程序 (2860) 运维 (2533) 服务器 (2698) 操作系统 (2325) 硬件开发 (2492) 嵌入式 (2955) 微软技术 (2769) 软件工程 (2056) 测试 (2865) 网络空间安全 (2948) 网络与通信 (2797) 用户体验设计 (2592) 学习和成长 (2593) 搜索 (2744) 开发工具 (7108) 游戏 (2829) HarmonyOS (2935) 区块链 (2782) 数学 (3112) 3C硬件 (2759) 资讯 (2909) Android (4709) iOS (1850) 代码人生 (3043) 阅读 (2841)

热门文章

101
推荐
关于我们 隐私政策 免责声明 联系我们
Copyright © 2020-2025 蚁人论坛 (iYenn.com) All Rights Reserved.
Scroll to Top