前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >G1 垃圾回收器

G1 垃圾回收器

原创
作者头像
Get
发布2024-03-21 22:49:59
900
发布2024-03-21 22:49:59

https://blog.51cto.com/u_15278282/3242908

https://blog.csdn.net/Xx__WangQi/article/details/117000262

G1 设计思路:

代码语言:java
复制
1、G1的设计原则是"首先收集尽可能多的垃圾(Garbage First)":
   G1并不会等内存耗尽(串行、并行)或者快耗尽(CMS)的时候开始垃圾收集,而是在内部采用了启发式算法,在老年代找出具有高收集收益的分区进行收集。
   同时G1可以根据用户设置的暂停时间目标自动调整年轻代和总堆大小,暂停目标越短年轻代空间越小、总空间就越大;
2、G1采用内存分区(Region)的思路:
   将内存划分为一个个相等大小的内存分区,回收时则以分区为单位进行回收,存活的对象复制到另一个空闲分区中。
   由于都是以相等大小的分区为单位进行操作,因此G1天然就是一种压缩方案(局部压缩);
   区可分为年轻代(Eden、Survivor)、老年代(Old)、大对象(Humongous),但每个分区都可能随G1的运行在不同代之间前后切换;
3、G1只有逻辑上的分代概念:
   G1虽然也是分代收集器,但整个内存分区不存在物理上的年轻代与老年代的区别,G1将内存在逻辑上划分为年轻代和老年代。
   其中年轻代又划分为Eden空间和Survivor空间。但年轻代空间并不是固定不变的,当现有年轻代分区占满时,JVM会分配新的空闲分区加入到年轻代空间。
4、G1的收集都是STW的,但年轻代和老年代的收集界限比较模糊,采用了混合(mixed)收集的方式。
   即每次收集既可能只收集年轻代分区(年轻代收集),也可能在收集年轻代的同时,包含部分老年代分区(混合收集),
   这样即使堆内存很大时,也可以限制收集范围,从而降低停顿。
1、分区(Region):G1采用了分区(Region)的思路,将整个堆空间分成若干个大小相等的内存区域,每次分配对象空间将逐段地使用内存(Region)。
                因此,在堆的使用上,G1并不要求对象的存储一定是物理上连续的,只要逻辑上连续即可;
                每个分区也不会确定地为某个代服务,可以按需在年轻代和老年代之间切换。
                启动时可以通过参数-XX:G1HeapRegionSize=n可指定分区大小(1MB~32MB,且必须是2的幂),默认将整堆划分为2048个分区。
2、卡片(Card):在每个分区内部又被分成了若干个大小为512 Byte卡片(Card);
              标识堆内存最小可用粒度分区(Region)中的所有卡片将会记录在全局卡片表(Global Card Table)中,分配的对象会占用物理上连续的若干个卡片;
              CardTable每个数组项对应一个Card,里面记录了对应的Card状态,如果当前Card里面存有对象,则将当前Card标记为1,Dirty(脏的)。
              当查找分区(Region)内对象的引用时便可通过记录卡片(RSet)来查找该引用对象。 
              每次对内存的回收,都是对指定分区的卡片(Card)进行处理。             

clipboard.png
clipboard.png
代码语言:java
复制
3、堆(Heap):G1同样可以通过-Xms/-Xmx来指定堆空间大小。
             当发生年轻代收集或混合收集时,通过计算GC与应用的耗费时间比,自动调整堆空间大小。
             如果GC频率太高,则通过增加堆尺寸,来减少GC频率,相应地GC占用的时间也随之降低;
                 目标参数-XX:GCTimeRatio即为GC与应用的耗费时间比,G1默认为9,而CMS默认为99,因为CMS的设计原则是耗费在GC上的时间尽可能的少。
             当空间不足,如对象空间分配或转移失败时,G1会首先尝试增加堆空间,如果扩容失败,则发起担保的Full GC。
                 Full GC后,堆尺寸计算结果也会调整堆空间。
4、巨型对象(Humongous):当一个大小达到甚至超过分区(Region)大小一半的对象称为巨型对象(Humongous Object)
    巨型对象会独占一个、或多个连续分区,其中第一个分区被标记为开始巨型(StartsHumongous),相邻连续分区被标记为连续巨型(ContinuesHumongous)。
    因为巨型对象的移动成本很高,而且有可能一个分区不能容纳巨型对象。因此,巨型对象会直接在老年代分配,所占用的连续空间称为巨型分区(Humongous Region)。
   G1内部做了一个优化,一旦发现没有引用指向巨型对象,则可直接在年轻代收集周期中被回收。
5、已记忆集合(RememberSet、RSet):G1为了避免STW式的整堆扫描,在每个分区记录了一个已记忆集合(RSet),记录(PRT)引用分区内对象的卡片(Card)索引。
                                当要回收该分区时,通过扫描分区的RSet,来确定引用本分区内的对象是否存活,进而确定本分区内的对象存活情况。
6、PerRegionTable(PRT):RSet在内部使用Per Region Table(PRT)记录分区(Region)的引用情况(哪些Region对象引用了当前Region的对象)
                        在PRT中将会以三种模式记录引用:
                                1、稀少:  直接记录引用对象的卡片索引
                                2、细粒度:记录引用对象的分区索引
                                3、粗粒度:只记录引用情况,每个分区对应一个比特位

clipboard.png
clipboard.png
代码语言:java
复制
7、收集集合(CollectionSet、CSet):代表每次 GC 暂停时,要回收的一系列目标分区。
   在任意一次收集暂停中,CSet 所有分区都会被释放,内部存活的对象都会被转移到分配的空闲分区中,无论是年轻代收集,还是混合收集,工作的机制都是一致的。
   年轻代收集CSet只容纳年轻代分区,而混合收集会通过启发式算法,在老年代候选回收分区中,筛选出回收收益最高的分区添加到CSet中。
   G1的收集都是根据CSet进行操作的,年轻代收集与混合收集没有明显的不同,最大的区别在于两种收集的触发条件。

clipboard.png
clipboard.png
代码语言:java
复制
8、年轻代收集集合(CSet of Young Collection):应用线程不断活动后,年轻代空间会被逐渐填满。
               当JVM分配对象到Eden区域失败(Eden区已满)时,便会触发一次STW式的年轻代收集。
               在年轻代收集中,Eden分区存活的对象将被拷贝到Survivor分区;
               原有Survivor分区存活的对象,将根据任期阈值(tenuring threshold,默认15)分别晋升到PLAB中,新的survivor分区和老年代分区。
               而原有的年轻代分区将被整体回收掉。
               同时,年轻代收集还负责维护对象的年龄(存活次数),辅助判断老化(tenuring)对象晋升的时候是到Survivor分区还是到老年代分区。
9、混合收集集合(CSet of Mixed Collection):年轻代收集不断活动后,老年代的空间也会被逐渐填充。
               当老年代占用空间超过整堆比IHOP阈值-XX:InitiatingHeapOccupancyPercent(默认45%)时,G1就会启动一次混合垃圾收集周期。
               为了满足暂停目标,G1可能不会一次将所有的候选分区收集掉,
               因此G1可能会产生连续多次的混合收集与应用线程交替执行,每次STW的混合收集与年轻代收集过程相类似。
10、RSet的维护:G1 需要一个增量式的完全标记并发算法,通过维护RSet,得到准确的分区引用信息
               在G1中,RSet的维护主要来源两个方面:写屏障(Write Barrier)和并发优化线程(Concurrence Refinement Threads) 
11、写屏障(Write Barrier):屏障是指在原生代码片段中,当某些语句被执行时,屏障代码也会被执行(如同SpringAOP)。
               而G1主要在赋值语句中,使用写前屏障(Pre-Write Barrrier)和写后屏障(Post-Write Barrrier)。
                        所谓的写屏障,其实就是指在赋值操作前后,加入一些处理(可以参考AOP的概念):
                        void oop_field_store(oop* field, oop new_value) {  
                            pre_write_barrier(field);          // 写屏障-写前操作
                            *field = new_value; 
                            post_write_barrier(field, value);  // 写屏障-写后操作
                        }

代码语言:java
复制

12、写前屏障(Pre-Write Barrrier):即将执行一段赋值语句时,等式左侧对象将修改引用到另一个对象,
                那么等式左侧对象原先引用的对象所在分区将因此丧失一个引用,那么JVM就需要在赋值语句生效之前,记录丧失引用的对象(记录旧的引用对象)
                JVM并不会立即维护RSet,而是通过批量处理,在将来RSet更新(见SATB)。
                        void pre_write_barrier(oop* field) {
                            oop old_value = *field;    // 获取旧值
                            remark_set.add(old_value); // 记录原来的引用对象
                        }
13、写前屏障(Post-Write Barrrier):当执行一段赋值语句后,等式右侧对象获取了左侧对象的引用,那么等式右侧对象所在分区的RSet也应该得到更新。
                同样为了降低开销,写后屏障发生后,RSet也不会立即更新,同样只是记录此次更新日志,在将来批量处理(见Concurrence Refinement Threads)
                      void post_write_barrier(oop* field, oop new_value) {  
                           remark_set.add(new_value);  // 记录新引用的对象
                      }
14、原始快照算法(Snapshot at the beginning 、SATB):
                主要针对标记-清除垃圾收集器的并发标记阶段,非常适合G1的分区块的堆结构,同时解决了CMS的主要烦恼:重新标记暂停时间长带来的潜在风险。
                1、SATB会创建一个对象图,相当于堆的逻辑快照,从而确保并发标记阶段所有的垃圾对象都能通过快照被鉴别出来。
                2、当赋值语句发生时,应用将会改变了它的对象图,那么JVM需要记录被覆盖的对象。
                3、因此写前屏障会在引用变更前,将值记录在SATB日志或缓冲区中(每个线程都会独占一个SATB缓冲区,初始有256条记录空间)
                4、当空间用尽时,线程会分配新的SATB缓冲区继续使用,而原有的缓冲去则加入全局列表中。
                5、最终在并发标记阶段,并发标记线程(Concurrent Marking Threads)在标记的同时,还会定期检查和处理全局缓冲区列表的记录,
                6、然后根据标记位图分片的标记位,扫描引用字段来更新RSet。
                此过程又称为并发标记/SATB写前屏障。
1、初始标记(Initial Mark):负责标记所有能被直接可达的根对象(原生栈对象、全局对象、JNI对象),根是对象图的起点,此时需要STW(用户线程暂停)
2、并发标记(Concurrent Mark):和用户线程并发执行,每个线程每次只扫描一个分区,从而标记出存活对象图。    
3、重新标记(Remark):STW后,去处理剩下的SATB日志缓冲区和所有更新,找出所有未被访问的存活对象,同时安全完成存活数据计算

G1 简介

https://blog.51cto.com/u_15278282/3242908

代码语言:java
复制
G1 (Garbage-First、年轻代 + 老年代)
GMS 使用的算法是:三色标记 + Incremental-Update + 写屏障
G1 使用的算法是:三色标记 + SATB + 写屏障
G1 是一款面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量内存的机器,以极高概率满足GC停顿时间要求的同时,还具备高吞吐量性能特征。
G1 是逻辑分代&物理分区模型,这种模型是将堆内存分为物理上的一个个的Region,每个 Region 大小1~32MB(默认为1MB),最多不能超过2048个Region;
 每个Region可以是以下任何一种分代空间,且在同一时间只能是一种分代空间:
G1 特点:
  1、同时注重吞吐量(Throughput)和低延迟(Low latency),默认的暂停目标是 200 ms
  2、超大堆内存,会将堆划分为多个大小相等的 Region(化整为零,对区域进行管理,如可以加快标记速度、复制速度等)
  3、整体上是 标记+整理 算法,两个区域之间是 复制 算法   
知识拓展:
1、G1 将Java堆划分为多个大小相等的独立区域 Region:
 默认Region大小等于堆大小除以2048,比如堆大小为4096M,则Region大小为2M,当然也可以用参数"-XX:G1HeapRegionSize"手动指定Region大小
 Region的区域功能是动态变化的:一个Region可能之前是年轻代,如果Region进行了垃圾回收,之后可能又会变成老年代
2、G1 新生代和老年代不再物理隔离,它们都是(不需要连续)Region的集合:
 E(Eden)、S(Survivor)、O(Old)、H(Humongous) 
 年轻代对堆内存的占比是5%,可以通过 “-XX:G1NewSizePercent” 设置新生代初始占比
 年轻代对堆内存的不会超过60%,可以通过 “-XX:G1MaxNewSizePercent” 调整新生代初始占比
 年轻代中的 Eden、Survivor 对应的 region 也跟之前一样,默认8:1:1
 Humongous区专门存放短期巨型对象,不用直接进老年代,可以节约老年代的空间,避免因为老年代空间不够的GC开销。
3、G1 垃圾收集器对于年轻代的存活对象什么时候会转移到老年代,跟之前讲过的原则一样,唯一不同的是对大对象的处理:
 大对象的判定规则:一个大对象超过了一个Region大小的50%,分配大对象到 Humongous 区
 Full GC的时候除了收集年轻代和老年代之外,也会将Humongous区一并回收。
G1 回收步骤:   
初始标记(initial mark,STW): 标记那些和GC ROOT直接连接的对象,存在STW,但是STW时间很短
并发标记(Concurrent Marking):同CMS的并发标记(GC线程和业务线程同时工作,标记出来哪些是垃圾 最终标记)
最终标记(Remark,STW):       同CMS的重新标记(因为并发标记会造成漏标(三色标记中会讲到),必须重新标记,存在STW)
筛选回收(Cleanup,STW):回收所有年轻代Region + 部分老年代Region
        因为这个过程中会产生STW,G1为了最大概率满足GC停顿时间的要求,会优先回收那些满足回收条件,
        且STW时间不超过最大停顿时间的老年代Region           
相关 JVM 参数:
-XX:+UseG1GC
-XX:G1HeapRegionSize=size
-XX:MaxGCPauseMillis=time               

clipboard.png
clipboard.png
clipboard.png
clipboard.png

G1-回收阶段

代码语言:java
复制
以下三个阶段是一个循环的过程:
1、年轻代回收(Minor GC):Eden 区满的时候进行回收(会 STW)
2、并发标记(Concurrent-mark):老年代占用堆空间比例达到阈值时,进行并发标记(不会 STW)(该阶段是个并发的标记的过程,顺便清理一点点对象)
3、混合收集(Mixed GC):真正的清理,发生在"混合模式",它不只清理年轻代,还会将老年代的一部分区域进行清理。
这三种模式之间的间隔也是不固定的。比如,1次Minor GC之后,发生了一次并发标记,接着发生了9次Mixed GC

clipboard.png
clipboard.png

G1-新生代回收-阶段

代码语言:java
复制
新生代垃圾回收(复制算法):
在 Young GC 时会进行 GC Root 的初始标记
回收过程:
1、将幸存对象复制拷贝进幸存区S
2、幸存区到年龄的对象晋升到老年代,不够年龄的拷贝到新的幸存区

clipboard.png
clipboard.png

G1-并发标记-阶段

代码语言:java
复制
并发标记:
当老年代占用堆空间比例达到阈值时,进行并发标记(不会 STW),由下面的 JVM 参数决定
并发标记:同CMS,只不过遍历范围缩小
-XX:InitiatingHeapOccupancyPercent=percent (默认45%)

clipboard.png
clipboard.png

G1-混合回收-阶段

代码语言:java
复制
混合回收阶段(Mixed GC、复制算法)
默认当老年代占整个堆大小的超过45%,触发MixedGC,回收所有年轻代Region + 部分老年代Region
Mixed GC 会对 E、S、O 进行全面垃圾回收
1、最终标记(Remark)会 STW:因为在并发标记时,用户线程会产生新的对象,存在漏标的情况,需要暂停补标
2、拷贝存活(Evacuation)会 STW
Mixed GC 会根据最大STW时间来有选择的进行垃圾回收:
1、筛选出回收价值最高的区域进行回收
2、一定时间内优先回收垃圾最多的区域
最大暂停时间:-XX:MaxGCPauseMillis=ms

clipboard.png
clipboard.png

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档