JVM G1垃圾回收器学习笔记

前言

最近在工作中遇到频繁FullGC且YoungGC时间有时特别长的情况,而自己对JVM的垃圾回收也是一知半解,因此需要对JVM做系统的了解,为快速解决工作中的问题,能有效分析GC日志和业务代码,先从G1垃圾回收器开始学习(工作中系统采用的就是G1垃圾回收器)

本文概览

在这里插入图片描述

准备阶段

什么是GC

GC全称Garbage Collection,意为垃圾收集。在系统不停机运行中,应用会不断创建对象,也就是不断的在内存中进行空间分配。但系统内存是一定的,不可能支持无限制的内存分配,因此会针对应用持续运行中不再使用的对象所占用的内存空间进行回收。

GC算法

常见的垃圾收集算法有以下几种:

  • 标记-复制
  • 标记-清除
  • 标记-整理

标记-复制

标记出存活对象后,将存活的对象复制到一块准备好的空闲内存区域,然后将垃圾回收的区域全部回收掉。

标记-复制示意图

上图以年轻代的回收场景来举例,可以看到,复制算法有两个比较重要的点:对象复制的过程、空间区域的冗余。如果一个区域中存活对象较多的场景不适合采用复制算法来进行垃圾回收。还有就是需要准备额外的空闲区域来接收存活对象的放置,因此该算法也有额外空间的损失。

标记-清除

标记出存活对象后,将死亡的对象直接被回收掉,存活对象不做处理。

标记-清除

上图我们也可以看到,这种处理方式很容易造成内存碎片的问题,例如如图所示,有16M的内存空间,通过标记-清除的回收算法,回收6M的内存空间,但无法分配一个连续的4M大小的对象。

标记-整理

标记出存活对象后,将死亡的对象直接回收掉并将存活对象做一步压缩整理,将其压缩到头部连续的空间内。

标记-整理算法

这就解决了上面说的内存碎片问题。

GC回收器

Java虚拟机发展至今,共有一下几款垃圾收集器:

  • 新生代
    • Serial:基于标记复制算法,使用一个收集线程去完成垃圾收集工作,并且在垃圾收集过程中必须暂停其他所有工作线程,直到收集工作结束
    • Parallel Scavenge:基于标记复制算法,使用多个线程去并行收集
    • ParaNew:基于标记复制算法,使用多个线程去并行收集,一般与CMS组成JVM分代回收的垃圾回收器
  • 老年代
    • Serial Old:基于标记整理算法,使用一个收集线程去完成垃圾收集工作,通常用于客户端模式下。在服务端模式下,通常作为CMS收集器发生失败时的后备预案。
    • Parallel Old:基于标记整理算法,使用多个线程去并行收集,垃圾回收的过程还是STW
    • CMS:基于标记清除算法,第一个真正意义上的并发垃圾回收器
  • 不分代
    • G1:基于CSet进行垃圾回收,这里不赘述,下面做详细解释
    • ZGC:它是一个可扩展的低延迟垃圾回收器,旨在处理多达几TB的堆内存,同时保持暂停时间在10毫秒以内,无论堆的大小如何。它使用了一种叫做颜色指针的技术来实现并发的对象整理,并极大地减少了STW暂停
    • Shanendoah:它是一个低延迟的垃圾回收器,设计目标是减少GC的暂停时间,而不仅仅关注整体的吞吐量。它通过并发地压缩堆和并发地进行标记-清除,达到了最小化STW暂停的效果,使其成为响应时间要求严格的应用的一个合适选择

G1出现的背景

Garbage First(简称G1)收集器是垃圾收集器技术发展历史上的里程碑式的结果,它开创了收集器面向局部收集的设计思路和基于Region的内存布局形式。摘自《深入理解Java虚拟机》

它主要应用于服务端的垃圾回收场景,提供软实时、低延时、可设定目标,适用于较大的堆内存(对比CMS,《深入理解Java虚拟机》作者是这么说的:在小内存应用上CMS的表现大概率仍然要会优于G1,而在大内存应用上G1则大多能发挥其优势,这个优劣势的Java堆容量平衡点通常在6GB至8GB之间)。

它的出现也是为了替换JDK 5中的CMS垃圾回收器。在JDK 9发布时将JDK默认的Parallel Scavenge+Parallel Old组合的垃圾回收器修改为G1收集器,而CMS则是被定义为不推荐使用的垃圾回收器。

G1发展历程

G1的内存布局

分区与分代

G1将Java堆分成若干个大小一致的区域,并且Region的大小可以通过-XX:G1HeapRegionSize=N参数设定,取值范围为1MB ~ 32MB,且应为2的N次幂。下图是G1的内存分布示意图(摘自于引用文章)。

内存布局

从示意图来看,G1仍然保留年轻代、老年代的概念,但新生代和老年代不再是固定的了,也就是同一块Region有可能是年轻代也有可能会被当做是老年代,并且他们在物理上也不是连续的了。图中还有Humongous区域,这个是用来存储大对象的区域,G1认为只要超过了Region大小的一半就是大对象,会被直接存储到该区域当中,它也属于老年代的一部分。

由于G1将垃圾回收的最小单位定位Region,这样就可以有计划地避免在整个Java堆中进行全区域的垃圾收集。G1收集器回去跟踪各个Region里面的垃圾堆积的"价值"(回收所获得的空间大小以及回收所需时间的经验值)大小,然后在后台维护一个优先级列表,每次根据用户设定允许的收集停顿时间(使用参数 -XX:MaxGCPauseMillis指定,默认值200)来优先处理回收价值收益最大的那些Region,这也是"Garbage First"名称的由来。

前面说了,G1会在逻辑上划分年轻代、老年代,其中年轻代又分为Eden、Survivor空间,但年轻代并不是固定不变的,当年轻代分区占满时,JVM会分配新的内存空间加入到年轻代中,整个年轻代内存会在初始空间-XX:G1NewSizePercent(默认整堆5%)与最大空间(默认60%)之间动态变化,且由参数目标暂停时间-XX:MaxGCPauseMillis(默认200ms)、需要扩缩容的大小以-XX:G1MaxNewSizePercent及分区的已记忆集合(RSet)计算得到。当然,G1依然可以设置固定的年轻代大小(参数-XX:NewRatio、-Xmn),但同时暂停目标将失去意义。

收集集合

既然空间上不连续,那G1是如何回收的呢?总不至于去全堆扫描吧?肯定不是的😄。这就引入了Collection Set(简称CSet)来表示每次GC暂停时回收的一系列目标Region区域。

CSet示意图

在G1中,共有两类CSet,一类为保存年轻代Region的集合,另一类为保存年轻代+老年代的Region的集合。年轻代收集集合则保存年轻代的分区,在GC时会将这些分区中的存活对象移动到空闲分区中,然后将原始分区空间全部回收掉。与年轻代集合不同是,老年代集合会有准入条件,可以通过活跃度阈值-XX:G1MixedGCLiveThresholdPercent(默认85%)进行设置,从而拦截那些回收开销巨大的对象;同时,每次混合收集可以包含候选老年代分区,可根据CSet对堆的总大小占比-XX:G1OldCSetRegionThresholdPercent(默认10%)设置数量上限来避免长时间的暂停。

其实,G1的收集都是根据CSet进行操作的,年轻代收集与混合收集没有明显的不同,最大的区别在于两种收集的触发条件。

记忆集与卡表

我们现在知道G1是通过CSet进行垃圾回收的,那CSet是如何被构建呢?其实针对年轻代Region来讲,一般会将所有分区(有对象分配的)加入到年轻代的CSet中,而对于老年代就比较复杂了,如果从GC Roots开始一直到叶子对象这一整条引用链都分布在一个Region当中就比较方便,在扫描当前Region时就可以将这条引用链标记出来,那么当跨Region引用时,G1是如何标记的呢?肯定不是全堆扫描🌚,答案就是G1会维护一个全局卡表,他本质上是一个哈希结构,Key为每个Region的起始地址,Value是一个集合,里面存储的元素是卡表的索引号。每个Region都会维护一个记忆集。

G1卡表模型

上图是一个关于卡表&记忆集的直观图,比较清晰直接,我在学习这块的时候十分困惑,所以也在这里记录下我的理解,如果有错误还请指正。

G1维护了一个全局卡表,这个卡表(代表整个堆)分成若干个卡,也就是分成若干个连续的物理内存区域。当应用线程修改一个对象引用时,涉及的卡便会被标记为"dirty"。而每个Region又维护一个记忆集,这个记忆集本质上是一个哈希结构,key为别的Region指向自己的指针,Value是一个集合,记录了卡表的索引号。这里解释下:比如对象A指向对象B,那么在B的记忆集中就是记录对象A的指针,并且将A所对应的卡片索引号集合(之所以是一个集合,是因为一个对象可能比较大,它占用了多个卡或者一个Region中有多个对象都引用当前Region的对象,它们设计多个卡的情况)

写屏障

讲述完记忆卡与卡表的实现,那么他们的数据是如何被更新维护的呢?实际上JVM会在编译时注入一小段代码,用于记录指针变化,object.field = <reference> (putfield) ;当更新指针时,会将对应的卡标记为Dirty,将Card加入到Dirty Card Queue,这个队列会有白/绿/黄/红四个颜色,代表JVM处理队列更新记忆集的紧急程度

  • 白:天下太平,无事发生。
  • 绿:-XX:G1ConcRefinementGreenZone=N,Refinement线程开始被激活,开始更新RS
  • 黄:-XX:G1ConcRefinementYellowZone=N,全部Refinement线程开始激活
  • 红:-XX:G1ConcRefinementRedZone=N,产生STW,应⽤线程也参与排空队列的⼯作

G1的垃圾回收原理

备注:对G1的垃圾回收原理是基于Jdk 8版本。

G1垃圾收集活动周期图

G1首先会先进行若干次young gc,每一次gc后,会将存活对象提供给PLAB(Promotion Local Allocation Buffer)来转移对象到老年代或Survivor区。当老年代堆内存占用达到阈值后(-XX:InitiatingHeapOccupancyPercent,默认45%)会进入到并发标记周期,这个周期G1会先进行一次young gc并且伴随着GC Roots的标记(栈中的对象、静态变量、常量、JNI对象等),随后进行与应用线程并发的进行存活对象标记。并发标记结束后,进行重新标记,这个时候标记一下在并发标记过程中产生对象引用更新(通过原始快照+记忆集快速实现),并发标记周期的最后一个操作就是清除阶段,该阶段是对RSet梳理、整理堆分区以及识别所有空闲分区,并发标记周期后,会产生多次mixed gc情况,并不会一次性收集完,这也是G1的启发式算法,根据用户设定的期望时间来优先回收价值最大的一批Region集合。

标记存活对象

通过三色标记法+原始快照,细节我们单拉一节来具体介绍。

Young GC

通过GC日志来看下,young gc各阶段都是干什么的

2023-09-15T16:07:33.481+0800: 158368.669: [GC pause (G1 Evacuation Pause) (young)
Desired survivor size 318767104 bytes, new threshold 15 (max 15)(to-space exhausted), 0.5207419 secs][Parallel Time: 316.3 ms, GC Workers: 13][GC Worker Start (ms): Min: 158368670.8, Avg: 158368673.3, Max: 158368674.0, Diff: 3.2][Ext Root Scanning (ms): Min: 0.4, Avg: 2.5, Max: 6.1, Diff: 5.7, Sum: 32.4][Update RS (ms): Min: 6.5, Avg: 15.8, Max: 23.6, Diff: 17.1, Sum: 204.8][Processed Buffers: Min: 24, Avg: 93.5, Max: 213, Diff: 189, Sum: 1216][Scan RS (ms): Min: 0.0, Avg: 1.2, Max: 1.7, Diff: 1.7, Sum: 16.1][Code Root Scanning (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0][Object Copy (ms): Min: 282.4, Avg: 291.0, Max: 300.7, Diff: 18.3, Sum: 3783.0][Termination (ms): Min: 0.0, Avg: 3.1, Max: 4.8, Diff: 4.8, Sum: 40.3][Termination Attempts: Min: 1, Avg: 1.7, Max: 5, Diff: 4, Sum: 22][GC Worker Other (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.3][GC Worker Total (ms): Min: 313.0, Avg: 313.6, Max: 316.1, Diff: 3.1, Sum: 4077.0][GC Worker End (ms): Min: 158368986.9, Avg: 158368986.9, Max: 158368987.0, Diff: 0.0][Code Root Fixup: 0.0 ms][Code Root Purge: 0.0 ms][Clear CT: 0.5 ms][Other: 204.0 ms][Evacuation Failure: 172.1 ms][Choose CSet: 0.0 ms][Ref Proc: 27.2 ms][Ref Enq: 0.6 ms][Redirty Cards: 1.2 ms][Humongous Register: 0.1 ms][Humongous Reclaim: 0.5 ms][Free CSet: 0.3 ms][Eden: 3704.0M(4808.0M)->0.0B(3024.0M) Survivors: 0.0B->0.0B Heap: 11.2G(12.0G)->7850.2M(12.0G)]
Heap after GC invocations=4016 (full 26):garbage-first heap   total 12582912K, used 8038625K [0x00000004e0800000, 0x00000004e1003000, 0x00000007e0800000)region size 8192K, 0 young (0K), 0 survivors (0K)Metaspace       used 159472K, capacity 162234K, committed 162684K, reserved 1193984Kclass space    used 16726K, capacity 17320K, committed 17532K, reserved 1048576K
}[Times: user=1.78 sys=0.04, real=0.52 secs]

young gc整个阶段是Stop The World的,日志中的并行时间其实说的是GC线程并行的工作,应用线程是被挂起的。解释完这个我们再来一块一块说明一下它的各个阶段都做了什么。

  • 并行时间
    • GC Worker Start:GC工作线程启动,后面的Diff指标越小越好,越小就意味着各个GC工作线程之间开始执行的时间间隔很短,或者它们几乎同时开始
      • 这能说明几点:更佳的并行性、更佳的响应性、更少的启动延迟、更一致的停顿时间
    • Ext Root Scanning:外部根扫描,GC工作线程并行扫描指向Collection Set的外部根(例如静态变量、Java线程、JNI引用)所耗费的时间
    • Update RS:更新记忆集,这个阶段主要是处理一些脏卡(在程序运行过程中,产生的一些引用的变更,这个阶段主要是确保跨Region引用在GC过程中被正确处理)
      • Processed Buffers:计数显示GC工作线程除了多少个’update buffers’
    • Scan RS:扫描记忆集以查找对某个Region的引用所花费的时间,这个时间取决RSet数据结构的"粗糙程度",G1底层采用的是哈希结构(前面描述记忆集已经提到)
    • Code Root Scanning:Code Cache扫描,这个阶段确保了所有直接或间接由机器代码持有的Java对象引用在年轻代GC过程中都被正确处理
    • Object Copy:存活对象的移动,这个阶段是GC线程并行去处理的,它占据了停顿时间的一大部分,如果这个时间很长,需要分析下为什么会产生这么多的存活对象
    • Termination:使得GC工作线程准备进入终止所花费的时间统计
    • GC Worker Other:GC工作线程没有在上面列出的任何并行活动中所花费的时间统计
  • 串行时间
    • Code Root Fixup:更新机器码所引用的对象的地址所耗费的时间
    • Code Root Purge:对一些无引用类进行卸载,删除无用代码进一步优化内存占用,这个操作所耗费的时间
    • Clear CT:清除卡表所耗费的时间
    • Other:其他阶段的耗时
      • Evacuation Failure:分配失败担保机制,当年轻代对象进行移动时找不到连续可用的空间,JVM就会做一系列动作,其中一个动作就是可能会产生一次全堆单线程、STW式的FullGC操作,因此我们要避免出现分配失败的情况。
      • Choose CSet:选择要被回收的Region集合所耗费的时间,在young gc中一般是将年轻代所有的Region加入进来
      • Ref Proc:处理软引用、弱引用、虚引用花费的时间,这块的算法比较复杂,我们只需要知道这些引用的回收时机即可
      • Ref Enq:将上面的引用识别出满足回收条件的并加入到对应的引用处理队列中,并进行清理所耗费的时间,这两个阶段我学习比较混淆,没找到有效资料,先记住这两个阶段就是保证这些引用类型能被回收阶段正确处理
      • Redirty Cards:重新脏化卡片。排队引用可能会更新RSet,所以需要对关联的Card重新脏化(Redirty Cards)
      • Humongous Register、Reclaim: 主要是对巨型对象回收的信息,youngGC阶段会对RSet中有引用的短命的巨型对象进行回收,巨型对象会直接回收而不需要进行转移(转移代价巨大,也没必要)
      • Free CSet:释放CSet,其中也会清理CSet中的RSet

并发标记周期

并发标记周期日志如下所示:

2023-09-18T14:11:36.891+0800: 11.294: [GC pause (Metadata GC Threshold) (young) (initial-mark)
Desired survivor size 243269632 bytes, new threshold 15 (max 15)
, 0.0451481 secs][Parallel Time: 17.9 ms, GC Workers: 13][GC Worker Start (ms): Min: 11294.1, Avg: 11301.5, Max: 11302.2, Diff: 8.1][Ext Root Scanning (ms): Min: 0.3, Avg: 2.2, Max: 8.1, Diff: 7.8, Sum: 28.0][Update RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0][Processed Buffers: Min: 0, Avg: 0.0, Max: 0, Diff: 0, Sum: 0][Scan RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0][Code Root Scanning (ms): Min: 0.0, Avg: 1.6, Max: 6.4, Diff: 6.4, Sum: 20.7][Object Copy (ms): Min: 0.5, Avg: 4.7, Max: 9.0, Diff: 8.5, Sum: 60.9][Termination (ms): Min: 0.0, Avg: 1.9, Max: 2.3, Diff: 2.3, Sum: 24.6][Termination Attempts: Min: 1, Avg: 57.7, Max: 157, Diff: 156, Sum: 750][GC Worker Other (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.2][GC Worker Total (ms): Min: 9.6, Avg: 10.3, Max: 17.7, Diff: 8.1, Sum: 134.4][GC Worker End (ms): Min: 11311.8, Avg: 11311.8, Max: 11311.9, Diff: 0.1][Code Root Fixup: 0.1 ms][Code Root Purge: 0.0 ms][Clear CT: 0.2 ms][Other: 26.9 ms][Choose CSet: 0.0 ms][Ref Proc: 26.4 ms][Ref Enq: 0.1 ms][Redirty Cards: 0.1 ms][Humongous Register: 0.0 ms][Humongous Reclaim: 0.0 ms][Free CSet: 0.1 ms][Eden: 240.0M(3680.0M)->0.0B(3664.0M) Survivors: 0.0B->16.0M Heap: 240.0M(12.0G)->15.0M(12.0G)]
Heap after GC invocations=1 (full 0):garbage-first heap   total 12582912K, used 15341K [0x00000004e0800000, 0x00000004e1003000, 0x00000007e0800000)region size 8192K, 2 young (16384K), 2 survivors (16384K)Metaspace       used 20508K, capacity 21162K, committed 21248K, reserved 1067008Kclass space    used 2541K, capacity 2793K, committed 2816K, reserved 1048576K
}[Times: user=0.18 sys=0.01, real=0.05 secs]
2023-09-18T14:11:36.937+0800: 11.339: [GC concurrent-root-region-scan-start]
2023-09-18T14:11:36.937+0800: 11.339: Total time for which application threads were stopped: 0.0454725 seconds, Stopping threads took: 0.0000165 seconds
2023-09-18T14:11:36.956+0800: 11.359: [GC concurrent-root-region-scan-end, 0.0194080 secs]
2023-09-18T14:11:36.956+0800: 11.359: [GC concurrent-mark-start]
2023-09-18T14:11:36.958+0800: 11.361: [GC concurrent-mark-end, 0.0020355 secs]
2023-09-18T14:11:36.958+0800: 11.361: [GC remark 2023-09-18T14:11:36.958+0800: 11.361: [Finalize Marking, 0.0194624 secs] 2023-09-18T14:11:36.978+0800: 11.381: [GC ref-proc, 0.0199802 secs] 2023-09-18T14:11:36.998+0800: 11.401: [Unloading, 0.0062853 secs], 0.0461724 secs][Times: user=0.18 sys=0.01, real=0.05 secs]
2023-09-18T14:11:37.005+0800: 11.407: Total time for which application threads were stopped: 0.0465892 seconds, Stopping threads took: 0.0003143 seconds
2023-09-18T14:11:37.005+0800: 11.407: [GC cleanup 26M->26M(12G), 0.0022687 secs][Times: user=0.01 sys=0.00, real=0.00 secs]

它是借助了一次young gc进行一次根对象的扫描,有一定的性能优化,对于young gc相关阶段不做赘述,这里记录下其他的阶段:

  • initial-mark:初始标记,标记GC Roots
  • GC concurrent-root-region-scan-start:主要的目标是扫描年轻代中的Survivor区域以查找指向老年代的引用。这是并发标记循环的早期部分,是在初始标记(STW)之后发生的。
    • 扫描Survivor区域:G1 GC并发地扫描Survivor区域,寻找那些指向老年代的引用。
    • 标记引用的对象:找到这些引用后,它们指向的老年代中的对象会被标记为存活。
    • 非STW阶段:这个阶段并发地与应用程序运行,不会导致应用程序停顿。
    • 时效性:此阶段需要在下一次年轻代GC发生之前完成,以确保所有的跨代引用都被正确标记。
  • GC concurrent-mark-start:通过可达性分析算法,将根对象引用链上的所有对象标记为存活。
  • GC remark:在该阶段中,G1需要一个暂停的时间,去处理剩下的SATB日志缓冲区和所有更新,找出所有未被访问的存活对象,同时安全完成存活数据计算

Mixed GC

2023-09-18T14:23:33.159+0800: 727.562: [GC pause (G1 Evacuation Pause) (mixed)
Desired survivor size 243269632 bytes, new threshold 15 (max 15)
- age   1:    7529224 bytes,    7529224 total(to-space exhausted), 0.5513782 secs][Parallel Time: 354.4 ms, GC Workers: 13][GC Worker Start (ms): Min: 727563.0, Avg: 727564.7, Max: 727565.7, Diff: 2.6][Ext Root Scanning (ms): Min: 0.2, Avg: 1.1, Max: 4.7, Diff: 4.5, Sum: 14.1][Update RS (ms): Min: 10.9, Avg: 13.9, Max: 16.2, Diff: 5.3, Sum: 180.2][Processed Buffers: Min: 20, Avg: 84.1, Max: 133, Diff: 113, Sum: 1093][Scan RS (ms): Min: 1.8, Avg: 4.5, Max: 5.6, Diff: 3.8, Sum: 58.5][Code Root Scanning (ms): Min: 0.0, Avg: 0.0, Max: 0.1, Diff: 0.1, Sum: 0.1][Object Copy (ms): Min: 297.8, Avg: 311.1, Max: 332.0, Diff: 34.3, Sum: 4044.3][Termination (ms): Min: 0.0, Avg: 21.4, Max: 33.1, Diff: 33.1, Sum: 278.6][Termination Attempts: Min: 1, Avg: 1.6, Max: 9, Diff: 8, Sum: 21][GC Worker Other (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.2][GC Worker Total (ms): Min: 351.0, Avg: 352.0, Max: 353.6, Diff: 2.6, Sum: 4575.9][GC Worker End (ms): Min: 727916.6, Avg: 727916.7, Max: 727917.3, Diff: 0.7][Code Root Fixup: 0.0 ms][Code Root Purge: 0.0 ms][Clear CT: 1.5 ms][Other: 195.4 ms][Evacuation Failure: 172.2 ms][Choose CSet: 0.1 ms][Ref Proc: 20.3 ms][Ref Enq: 0.1 ms][Redirty Cards: 0.5 ms][Humongous Register: 0.0 ms][Humongous Reclaim: 0.4 ms][Free CSet: 0.2 ms][Eden: 2232.0M(3672.0M)->0.0B(3680.0M) Survivors: 8192.0K->0.0B Heap: 11.3G(12.0G)->9434.1M(12.0G)]
Heap after GC invocations=38 (full 0):garbage-first heap   total 12582912K, used 9660532K [0x00000004e0800000, 0x00000004e1003000, 0x00000007e0800000)region size 8192K, 0 young (0K), 0 survivors (0K)Metaspace       used 153978K, capacity 156654K, committed 157000K, reserved 1189888Kclass space    used 16554K, capacity 17174K, committed 17224K, reserved 1048576K
}[Times: user=1.49 sys=0.03, real=0.55 secs]

mixed gc过程其实是和年轻代gc差不多的,区别就在于CSet的选择。young gc会选择年轻代所有的Region,mixed gc会根据期望停顿时间来选择最有收益的一批Region集合。这里不在赘述了。

总结

好了,对G1垃圾回收器的学习就暂时到这里了,对其有了大概的印象,包括G1垃圾回收的活动周期(young gc、并发标记周期、多次mixed gc)、RSet、CSet以及卡表等知识有了一定的概念,也学习了如何分析G1的GC日志。上面的内容大多是来源与网上的资料,在一些比较难懂的地方加上自己的理解并绘出直观图和语言注释。

参考

https://pdai.tech/md/java/jvm/java-jvm-gc-g1.html

https://www.infoq.com/articles/G1-one-Garbage-Collector-To-Rule-Them-All/

https://time.geekbang.org/column/intro/100010301

《深入理解Java虚拟机》

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/141804.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

贪心算法-

代码随想录 什么是贪心 贪心的本质是选择每一阶段的局部最优&#xff0c;从而达到全局最优。 这么说有点抽象&#xff0c;来举一个例子&#xff1a; 例如&#xff0c;有一堆钞票&#xff0c;你可以拿走十张&#xff0c;如果想达到最大的金额&#xff0c;你要怎么拿&#xff…

linux驱动之input子系统简述

文章目录 一、什么是input子系统二、内核代码三、代码分析 一、什么是input子系统 Input驱动程序是linux输入设备的驱动程序&#xff0c;我们最常见的就按键&#xff0c;触摸&#xff0c;插拔耳机这些。其中事件设备驱动程序是目前通用的驱动程序&#xff0c;可支持键盘、鼠标…

邮件营销方案

互联网的快速发展&#xff0c;使得新媒体营销、短视频营销、微信营销等新型营销方式成为主流。但是邮件营销仍然是性价比很高的营销方式之一&#xff0c;它不仅可以帮助你与潜在客户建立联系、传达信息并促进销售&#xff0c;同时也是维系老客户的重要手段之一。特别是对于外贸…

解答嵌入式和单片机的关系

嵌入式系统是一种特殊的计算机系统&#xff0c;用于特定任务或功能。而单片机则是嵌入式系统的核心部件之一&#xff0c;是一种在单个芯片上集成了处理器、内存、输入输出接口等功能的微控制器。刚刚好我这里有一套单片机保姆式教学&#xff0c;里面有编程教学、问题讲解、语言…

C++:优先级队列模拟实现和仿函数的概念使用

文章目录 使用方法Compare仿函数一些场景模板参数和函数参数 本篇总结优先级队列 使用方法 首先在官网查看它的一些用法 template <class T, class Container vector<T>,class Compare less<typename Container::value_type> > class priority_queue;从…

【golang】深入理解Go语言垃圾回收(GC)

垃圾回收 垃圾回收版本1.3之前标记-清除&#xff08;mark and sweep&#xff09;算法标记-清除&#xff08;mark and sweep&#xff09;的缺点 版本1.5的三色并发标记法没有STW的三色标记法屏障机制强-弱 三色不等式插入屏障删除屏障 版本1.8的混合写屏障&#xff08;hybrid wr…

【计算机网络】——数据链路层(应用:局域网、广域网、设备 )

//仅做个人复习和技术交流&#xff0c;图片取自王道考研&#xff0c;侵删 一、大纲 1、介质访问控制 信道划分介质访问控制 随机访问介质访问控制 2、局域网 3、广域网 4、数据链路层设备 二、局域网 1、局域网基本概念和体系结构 局域网(LocalArea Network): 简称LAN&…

[maven] 实现使用 plugin 及 properties 简述

[maven] 实现&使用 plugin 及 properties 简述 这章内容&#xff0c;我个人感觉可看可不看……&#xff1f; 不过课都上了&#xff0c;笔记 &#x1f4d2; 补完才对得起自己嘛 plugins 主要讲一下 maven 的 plugin 时怎么实现的&#xff0c;以及项目中怎么调用自己实现…

实现电商跨平台订单每日自动对账

场景描述&#xff1a; 多数商家都存在多电商平台同时经营的情况&#xff0c;而进行订单对账则是相关业务或财务人员的每日必修课。比如商家在天猫&#xff0c;苏宁&#xff0c;1号店&#xff0c;京东等均有运营店铺&#xff0c;每天需要通过各电商后台系统抓单打单&#xff0c…

若依cloud -【 100 ~ 103 】

100 分布式日志介绍 | RuoYi 分布式日志就相当于把日志存储在不同的设备上面。比如若依项目中有ruoyi-modules-file、ruoyi-modules-gen、ruoyi-modules-job、ruoyi-modules-system四个应用&#xff0c;每个应用都部署在单独的一台机器里边&#xff0c;应用对应的日志的也单独存…

springboot如何接入netty,实现在线统计人数?

springboot如何接入netty&#xff0c;实现在线统计人数&#xff1f; Netty 是 一个异步事件驱动的网络应用程序框架 &#xff0c;用于快速开发可维护的高性能协议服务器和客户端。 Netty ​ 是一个 NIO 客户端服务器框架 ​&#xff0c;可以快速轻松地开发协议服务器和客户端等…

微表情识别API + c++并发服务器系统

微表情识别API c并发服务器系统 该项目只开源c并发服务器程序&#xff0c;模型API部分不开源 地址&#xff1a;https://github.com/lin-lai/-API- 更新功能 4.1版本 改用epoll实现IO多路复用并发服务器 项目介绍 本项目用于检测并识别视频中人脸的微表情 目标任务: 用户上…

【李沐深度学习笔记】线性代数

课程地址和说明 线性代数p1 本系列文章是我学习李沐老师深度学习系列课程的学习笔记&#xff0c;可能会对李沐老师上课没讲到的进行补充。 线性代数 标量 标量&#xff08;scalar&#xff09;&#xff0c;亦称“无向量”。有些物理量&#xff0c;只具有数值大小&#xff0c…

基于微信小程序的校园失物招领系统设计与实现(源码+lw+部署文档+讲解等)

前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师、全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战✌&#x1f497; &#x1f447;&#x1f3fb;…

Qt创建线程(使用moveToThread方法创建子线程)

1.moveTothread方法: &#xff08;1&#xff09;要使用moveToThread方法必须继承与QObject类 &#xff08;2&#xff09;创建任务对象时不能指定父对象 例子&#xff1a; MyWork* work new MyWork(this); // error MyWork* work new MyWork; // ok &#xff08;3&#…

北工大汇编题——分支程序设计

题目要求 信息检素程序设计&#xff1a;在数据区&#xff0c;有9个不同的信息&#xff0c;编号 0-8&#xff0c;每个信息包括20 个字符。从键盘接收 0-8 之间的一个编号&#xff0c;然后再屏幕上显示出相应编号的信息内容&#xff0c;按“q”键退出 完整代码 DATAS SEGMENTn0…

搭建SpringBoot项目三种方式(超详细版)

目录 一、官网下载压缩包解压 二、通过Idea脚手架搭建 三、Spring Boot项目结构 3.1 pom.xml文件 3.2 启动类 3.3 配置文件 四、通过创建Maven项目添加依赖 一、官网下载压缩包解压 接下来我们搭建一个SpringBoot项目&#xff0c;并引入SpringMVC的功能&#xff0c;首先…

自学WEB服务器搭建01-安装Express+Node.js框架完成Hello World!

一、前言&#xff0c;网站开发扫盲知识 1.网站搭建开发包括什么&#xff1f; 前端、后端&#xff08;服务端&#xff09;数据库 前端开发主要涉及用户界面&#xff08;UI&#xff09;和用户体验&#xff08;UX&#xff09;&#xff0c;负责实现网站的外观和交互逻辑。前端开发…

多线程进阶:常见的锁策略、CAS

之前我们所了解的属于多线程的初阶内容。今天开始&#xff0c;我们进入多线程进阶的学习。 锁的策略 乐观锁 悲观锁 这不是两把具体的锁&#xff0c;应该叫做“两类锁” 乐观锁&#xff1a;预测锁竞争不是很激烈&#xff08;这里做的工作可能就会少一些&#xff09; 悲观锁…

3.6+铁死亡+WGCNA+机器学习

今天给同学们分享一篇3.6铁死亡WGCNA机器学习的生信文章“Identification of ferroptosis related biomarkers and immune infiltration in Parkinsons disease by integrated bioinformatic analysis”&#xff0c;这篇文章于2023年3月14日发表在BMC Med Genomics期刊上&#…