JVM G1垃圾回收器学习笔记

news2024/11/26 4:39:04

前言

最近在工作中遇到频繁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 1193984K
  class 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 1067008K
  class 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 1189888K
  class 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.coloradmin.cn/o/1023829.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【论文阅读】检索增强发展历程及相关文章总结

文章目录 前言Knn-LMInsightMethodResultsDomain AdaptionTuning Nearest Neighbor Search Analysis REALMInsightsMethodKnowledge RetrieverKnowledge-Augmented Encoder ExpResultAblation StudyCase Study DPRInsightMethodExperimentsResults RAGInsightRAG-Sequence Mode…

小程序中如何查看会员的访问记录

​在小程序中&#xff0c;我们可以通过如下方式来查看会员的访问记录。下面是具体的操作流程&#xff1a; 1. 找到指定的会员卡。在管理员后台->会员管理处&#xff0c;找到需要查看访客记录的会员卡。也支持对会员卡按卡号、手机号和等级进行搜索。 2. 查看会员卡详情。点…

RK3588平台开发系列讲解(项目篇)视频监控之RTMP推流

文章目录 一、RTMP协议是什么二、RTMP 的原理三、Nginx 流媒体服务器四、FFmpeg 推流沉淀、分享、成长,让自己和他人都能有所收获!😄 📢 目前常见的视频监控和视频直播都是使用了 RTMP、RTSP、HLS、MPEG-DASH、WebRTC流媒体传输协议等。 视频监控项目组成,分为三部分:…

PHP-composer安装扩展安装,批量操作合并pdf

清除Composer缓存&#xff1a; 运行以下命令来清除Composer的缓存&#xff0c;并再次尝试安装包。 bash composer clear-cache 使用不同的镜像源&#xff1a; Composer使用的默认包源可能会受到限制或访问问题。你可以切换到使用其他镜像源&#xff0c;如阿里云、Composer中国…

uni-app:实现等待加载功能

例子 下例是实现蓝牙连接的部分代码&#xff0c;先进行加载连接显示&#xff0c;在进行连接&#xff0c;连接成功/失败&#xff0c;都自动关闭加载效果 效果 核心 开始的加载效果&#xff1a; uni.showLoading({title: 正在连接&#xff0c;请稍候...,mask: true, }); 关闭…

进程转态及其转换过程

一.进程转态及其转换过程 在 Linux 操作系统中&#xff0c;进程的状态可以相互转换&#xff0c;下面是不同状态之间的相互转换&#xff1a; 就绪态&#xff08;Ready State&#xff09;&#xff1a;当一个进程创建后&#xff0c;它被放入就绪态。此时&#xff0c;进程已经被加…

Docker Compose初使用

简介 Docker-Compose项目是Docker官方的开源项目&#xff0c;负责实现对Docker容器集群的快速编排。 Docker-Compose将所管理的容器分为三层&#xff0c;分别是 工程&#xff08;project&#xff09;&#xff0c;服务&#xff08;service&#xff09;以及容器&#xff08;cont…

密码学概论

1.密码学的三大历史阶段&#xff1a; 第一阶段 古典密码学 依赖设备&#xff0c;主要特点 数据安全基于算法的保密&#xff0c;算法不公开&#xff0c;只要破译算法 密文就会被破解&#xff0c; 在1883年第一次提出 加密算法应该基于算法公开 不影响密文和秘钥的安全&#xff…

《Kubernetes部署篇:Ubuntu20.04基于外部etcd+部署kubernetes1.25.14集群(多主多从)》

一、部署架构图 1、架构图如下所示: 2、部署流程图如下所示: 二、环境信息 1、资源下载基于外部etcd+部署容器版kubernetes1.25.14集群资源合集 2、部署规划主机名K8S版本系统版本内核版本IP地址备注k8s-master-121.25.14Ubuntu 20.04.5 LTS5.15.0-69-generic192.168.1.12ma…

【音视频】AAC音频压缩格式

AAC音频压缩格式 ADTSvsADIF 总结&#xff1a; ADTS可以在任意帧解码&#xff0c;也就是说它每一顿都有头信息【默认使用格式】 ADIF只有一个统一的头&#xff0c;所以必须得到所有的数据后解码【一般不使用】 每一帧结构 每一帧的ADTS的头文件都包含了音频的采样率&#x…

【Linux学习】01Linux初识与安装

Linux&#xff08;B站黑马&#xff09;学习笔记 01Linux初识与安装 文章目录 Linux&#xff08;B站黑马&#xff09;学习笔记前言01Linux初识与安装操作系统简述Linux初识虚拟机介绍安装VMware Workstation虚拟化软件VMware中安装CentOS7 Linux操作系统下载CentOS操作系统VMwa…

kafka安装部署,和基本操作

kafka下载地址&#xff1a;Apache Kafka 我这里下载3.5.1 ​ 2、通过rz命令上传到linux服务器 3、解压 tar -zxvf kafka_2.12-3.5.1.tgz 4、在config目录下修改配置文件server.properties 主要修改这两处&#xff1a; #监听的端口advertised.listenersPLAINTEXT://自己…

【音视频】MP4封装格式

基本概念 使用MP4box.js查看MP4内部组成结构 整体结构 数据索引&#xff08;moov&#xff09;数据流包&#xff08;mdat&#xff09; 各个包的位置&#xff0c;大小&#xff0c;信息&#xff0c;时间戳&#xff0c;编码方式等全在数据索引 数据流包只有纯二进制码流数据 数据…

Linux基础开发工具

目录 前言 一、yum —— 软件包管理工具 1.1 yum的简介 1.2 yum的使用 1.3 yum源更新 二、vim —— 文本编辑器 2.1 vim的基本概念 2.2 vim的基本操作 2.3 vim正常模式命令集 2.4 vim末行模式命令集 extra vim补充 extra1 批量化注释 extra2 创建文件的…

腾讯云微服务平台 TSF 异地多活单元化能力重磅升级

导语 2023腾讯全球数字生态大会已于9月7-8日完美落幕&#xff0c;40专场活动展示了腾讯最新的前沿技术、核心产品、解决方案。 微服务与消息队列专场&#xff0c;腾讯云微服务平台 TSF 产品经理张桢带来了《腾讯云微服务平台 TSF 异地多活单元化能力重磅升级》的精彩演讲。本…

【LeetCode热题100】--48.找到字符串中所有字母异位词

48.找到字符串中所有字母异位词 使用滑动窗口&#xff1a; 由于字符串p的异位词的长度一定与字符串p的长度相同&#xff0c;所以我们可以在字符串s中构造一个长度为字符串p的长度相同的滑动窗口&#xff0c;并在滑动中维护窗口中每种字母的数量&#xff1b;当窗口中每种字母的数…

编译原理 —— 编译器

文章目录 编译原理阶段词法分析器语法分析器语义分析器中间代码生成器代码优化器代码生成器 编译原理阶段 编译器分为9个阶段来将我们所编写的高级代码编译为计算机可执行的机器码 源程序词法分析器语法分析器语义分析器中间代码生成器独立于机器的代码优化器代码生成器依赖于…

activiti流程变量

activiti流程变量 定义 流程变量在Activiti 中是一个十分重要的角色&#xff0c;流程运转时&#xff0c;需要靠流程变量&#xff0c;业务系统和activiti 结合时少不了流程变量&#xff0c;流程变量就是activiti 在管理工作流时根据管理需要而设置的变量。比如&#xff1a;在出…

JADE盲分离算法仿真

JADE算法原理 JADE 算法首先通过去均值预白化等预处理过程得到解相关的混合信号&#xff0c;预处理后的信号构建的协方差矩阵变为单位阵&#xff0c;为后续的联合对角化奠定基础&#xff1b;其次&#xff0c;通过建立四阶累积量矩阵&#xff0c;利用高阶累积量的统计独立性等性…

React+Node——next.js 构建前后端项目

一、安装全局依赖 npm i -g create-next-app二、创建next项目 create-next-app react-next-demo //或 create-next-app react-next-demo --typescript三、加载mysql依赖 npm i -S mysql2四、运行项目 npm run dev五、创建db文件目录&#xff0c;目录下创建index.ts import…