首页 > 其他分享 >G1垃圾回收器原理

G1垃圾回收器原理

时间:2024-11-06 22:15:08浏览次数:3  
标签:G1 标记 对象 回收 GC 引用 Root 垃圾

G1垃圾回收器原理

G1垃圾回收有两种方式:

1、年轻代回收(Young GC)

2、混合回收(Mixed GC)

年轻代回收

年轻代回收只扫描年轻代对象(Eden + Survivor),所以从GC Root到年轻代的对象或者年轻代对象引用了其他年轻代的对象都很容易扫描出来。

 

这里就存在一个问题,年轻代回收只扫描年轻代对象(Eden + Survivor),如果有老年代中的对象引用了年轻代中的对象,我们又如何知道呢?

比如上图中,E对象被对象引用了,那么显然在垃圾回收时E对象是不应该被回收的。

方案1:从GC Root开始,扫描所有对象,如果年轻代对象在引用链上,就标记为存活。

重新扫描一遍GC Root关联的所有对象,包括老年代的。这个方案显然不可行,需要遍历引用链上所有对象,效率太低。

方案2:维护一个详细的表,记录哪个对象被哪个老年代引用了。在年轻代中被引用的对象,不进行回收。

如上图中,通过引用详情表记录F和E对象分别被A和B对象引用了。

问题:

  1. 如果对象太多这张表会占用很大的内存空间。
  2. 存在错标的情况

方案2的第一次优化:只记录Region被哪些对象引用了。这种引用详情表称为记忆集 RememberedSet(简称RS或RSet):是一种记录了从非收集区域对象(这里可以看做老年代的对象)引用收集区域对象(这里可以看做年轻代的对象)的这些关系的数据结构。扫描时将记忆集中的对象也加入到GC Root中,就可以根据引用链判断哪些对象需要回收了。

问题:如果区域中引用对象很多,还是占用很多内存。

方案2的第二次优化:将所有区域中的内存按一定大小划分成很多个,每个块进行编号。记忆集中只记录对块的引用关系。如果一个块中有多个对象,只需要引用一次,减少了内存开销。

每一个Region都拥有一个自己的卡表(Card Table),如果产生了跨代引用(老年代引用年轻代),此时这个Region对应的卡表上就会将字节内容进行修改,JDK8源码中0代表被引用了称为脏卡。这样就可以标记出当前Region被老年代中的哪些部分引用了。那么要生成记忆集就比较简单了,只需要遍历整个卡表,找到所有脏卡。

那么怎么样去维护这个卡表呢?或者说怎么知道A对F引用了?

JVM使用写屏障(Write Barrier)技术,在执行引用关系建立的代码时,可以在代码前和代码后插入一段指令,从而维护卡表。

记忆集中不会记录新生代到新生代的引用,同一个Region中的引用也不会记录。

记忆集的生成流程分为以下几个步骤:

1、通过写屏障获得引用变更的信息。

2、将引用关系记录到卡表中,并记录到一个脏卡队列中。

3、JVM中会由Refinement 线程定期从脏卡队列中获取数据,生成记忆集。不直接写入记忆集的原因是避免过多线程并发访问记忆集。

执行流程:

更详细的分析下年轻代回收的步骤,整个过程是STW的:

1、Root扫描,将所有的静态变量、局部变量扫描出来。

2、处理脏卡队列中的没有处理完的信息,更新记忆集的数据,此阶段完成后,记忆集中包含了所有老年代对当前Region的引用关系。

3、标记存活对象。记忆集中的对象会加入到GC Root对象集合中,在GC Root引用链上的对象也会被标记为存活对象。

4、根据设定的最大停顿时间,选择本次收集的区域,称之为回收集合Collection Set。

5、复制对象:将标记出来的对象复制到新的区中,将年龄加1,如果年龄到达15则晋升到老年代。老的区域内存直接清空。

6、处理软、弱、虚、终结器引用,以及JNI中的弱引用。

G1年轻代回收核心技术

1、卡表 Card Table

每一个Region都拥有一个自己的卡表,卡表是一个字节数组,如果产生了跨代引用(老年代引用年轻代),G1会将卡表上引用对象所在的位置字节内容进行修改为0, 称为脏卡。卡表的主要作用是生成记忆集。

卡表会占用一定的内存空间,堆大小是1G时,卡表大小为1G = 1024 MB / 512 = 2MB

2、记忆集 RememberedSet(简称RS或RSet)

每一个Region都拥有一个自己的记忆集,如果产生了跨代引用,记忆集中会记录引用对象所在的卡表位置。标记阶段将记忆集中的对象加入GC ROOT集合中一起扫描,就可以将被引用的对象标记为存活。

3、写屏障 Write Barrier

G1使用写屏障技术,在执行引用关系建立的代码执行后插入一段指令,完成卡表的维护工作。

会损失一部分的性能,大约在5%~10%之间。

混合回收

多次回收之后,会出现很多Old老年代区,此时总堆占有率达到阈值(默认45%)时会触发混合回收MixedGC。

混合回收会由年轻代回收之后或者大对象分配之后触发,混合回收会回收 整个年轻代 + 部分老年代。

老年代很多时候会有大量对象,要标记出所有存活对象耗时较长,所以整个标记过程要尽量能做到和用户线程并行执行。

混合回收的步骤:

1、初始标记,STW,采用三色标记法标记从GC Root可直达的对象。

2、并发标记,并发执行,对存活对象进行标记。

3、最终标记,STW,处理SATB相关的对象标记。

4、清理,STW,如果区域中没有任何存活对象就直接清理。

5、转移,将存活对象复制到别的区域。

初始标记

初始标记会暂停所有用户线程,只标记从GC Root可直达的对象,所以停顿时间不会太长。采用三色标记法进行标记,三色标记法在原有双色标记(黑也就是1代表存活,白0代表可回收)增加了一种灰色,采用队列的方式保存标记为灰色的对象。

黑色:存活,当前对象在GC Root引用链上,同时他引用的其他对象也都已经标记完成。

灰色:待处理,当前对象在GC Root引用链上,他引用的其他对象还未标记完成。

白色:可回收,不在GC Root引用链上。

初始所有对象都是默认为白色,初始值为0:

三色标记中的黑色和白色是使用位图(bitmap)来实现的,比如8个字节使用1个bit来标识标记的内容,黑色为1,白色为0,灰色不会体现在位图中,会单独放入一个队列中。如果对象超过8个字节,仅仅使用第一个bit位处理。

将GC Root可以直到的对象D标记,D没有其他引用对象,所以直接标记为为黑色:

接下来将B对象标记,由于B关联了A和C,而A和C没有标记完成,所以B是待处理状态,将B送入灰色队列。

并发标记

接下来进入并发标记阶段,继续进行未完成的标记任务。此阶段和用户线程并发执行。

从灰色队列中获取尚未完成标记的对象B。标记B关联的A和C对象,由于A和C对象并未引用其他对象,可以直接标记成黑色,而B也完成了所有引用对象的标记,也标记为黑色。

最后从队列获取C对象,标记为黑色,E也标记为黑色。所以剩余对象F就是白色,可回收。

最后从队列获取C对象,标记为黑色,E也标记为黑色。所以剩余对象F就是白色,可回收。

三色标记存在一个比较严重的问题,由于用户线程可能同时在修改对象的引用关系,就会出现错标的情况,比如:

这个案例中正常情况下,B和C都会被标记成黑色。但是在BC标记前,用户线程执行了 B.c = null;将B到C的引用去除了。

同时执行了A.c = c; 添加了A到C的引用。此时会出现严重问题,C是白色可回收一旦回收代码中再去使用对象会造成重大问题。

如果接着处理B:

B在GC引用链上,没有引用任何对象,所以B标记为黑色:

这样C虽然在引用链上,但是被回收了。

G1为了解决这个问题,使用了SATB技术(Snapshot At The Beginning, 初始快照)。SATB技术是这样处理的:

1、标记开始时创建一个快照,记录当前所有对象,标记过程中新生成的对象直接标记为黑色。

2、采用前置写屏障技术,在引用赋值前比如B.c = null之前,将之前引用的对象c放入SATB待处理队列中。SATB队列每个线程都有一个,最终会汇总到一个大的SATB队列中。

最终队列处理完之后,C和F就可以完成标记了。

SATB的缺点是在本轮清理时可能会将不存活的对象标记成存活对象,产生了一些所谓的浮动垃圾,等到下一轮清理时才能回收。比如图中的E对象。

SATB练习题

C和E对象会被加入SATB队列中,最终被标记为存活。

转移的步骤如下:

1、根据最终标记的结果,可以计算出每一个区域的垃圾对象占用内存大小,根据停顿时间,选择转移效率最高(垃圾对象最多)的几个区域。

2、转移时先转移GC Root直接引用的对象,然后再转移其他对象。

先转移A对象:

接下来转移B对象:

3、回收老的区域,如果外部有其他区域对象引用了转移对象,也需要重新设置引用关系。

多次回收之后,会出现很多Old老年代区,此时总堆占有率达到阈值(默认45%)时会触发混合回收MixedGC。

混合回收会由年轻代回收之后或者大对象分配之后触发,混合回收会回收 整个年轻代 + 部分老年代。

老年代很多时候会有大量对象,要标记出所有存活对象耗时较长,所以整个标记过程要尽量能做到和用户线程并行执行。

混合回收的步骤:

1、初始标记,STW,采用三色标记法标记从GC Root可直达的对象。

2、并发标记,并发执行,对存活对象进行标记。

3、最终标记,STW,处理SATB相关的对象标记。

4、清理,STW,如果区域中没有任何存活对象就直接清理。

5、转移,将存活对象复制到别的区域。

标签:G1,标记,对象,回收,GC,引用,Root,垃圾
From: https://blog.csdn.net/jonas80029735/article/details/143581165

相关文章

  • SpringBoot小区垃圾分类系统62nc6(程序+源码+数据库+调试部署+开发环境)
    本系统(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。系统程序文件列表开题报告内容一、研究背景随着城市化进程的推进,小区垃圾处理问题日益突出。传统的垃圾处理方式不仅效率低下,而且对环境造成了严重污染。垃圾分类作为一种有效的......
  • 如何使用深度学习框架(PyTorch)来训练——147913张图像的超大超详细垃圾分类数据集,并附
    超大超详细垃圾分类数据集(分类,分类),共4大类,345小类,147913张图,已全部分类标注完成,共12GB。厨余垃圾76小类35058张可回收物195类86116张其他垃圾53类16156张有害垃圾18小类10583张 如何使用深度学习框架(如PyTorch)来训练一个包含147913张图像的超大超详细垃圾分类......
  • GC垃圾回收机制
    一、垃圾确定方式1.引用计数法: 每次对象被引用时,该对象的计数器都会+1,取消引用   计数器就会-1,当堆中的对象没有引用次数时就会被确定为垃圾,但是如果对象之间存在交叉引用时就无法被认为是垃圾  2.可达性分析法:  判断一个对象是否由从堆内到堆外的引用,没有则会被......
  • 《JVM第8课》垃圾回收算法
    为什么要进行垃圾回收?垃圾是指JVM中没有任何引用指向它的对象,如果不及时清理这些垃圾对象,那么它就会一直占用内存,如果垃圾对象越来越多,就会出现OOM了。要判断对象是否是垃圾对象有两种方式,一、引用计数法。二、可达性分析法。而要清除垃圾对象有三种常用方式,一、标记-清除算......
  • 基于Java+SpringBoot+Vue+HTML5城市垃圾分类管理系统(源码+LW+调试文档+讲解等)/城市
    博主介绍......
  • 垃圾堆火焰烟雾检测系统
    垃圾堆火焰烟雾检测系统基于先进的图像处理技术,垃圾堆火焰烟雾检测系统通过现场已有的监控摄像头采集垃圾场现场视频。通过高效的图像处理算法,系统能够实时分析视频内容,准确识别出垃圾堆是否发生烟雾或火焰的存在。一旦发现异常情况,系统会自动发出警报,提醒管理人员及时处理,从而实......
  • 关于JVM的垃圾回收
    垃圾回收主要回收的是堆中的实例、数组。STW(stoptheworld)暂停所有应用程序的线程,等待垃圾回收完成 1.对象什么时候可以被垃圾回收器回收一个对象如果没有任何的引用指向他了,那么他现在就是不可达对象(垃圾),如果定位了垃圾,那么垃圾回收器就可能会将他回收。(比如这个对象被显......
  • java计算机毕业设计城市垃圾分类回收管理系统(开题+程序+论文)
    本系统(程序+源码)带文档lw万字以上 文末可获取一份本项目的java源码和数据库参考。系统程序文件列表开题报告内容城市垃圾分类回收管理系统的相关研究一、研究背景随着城市化进程的迅猛发展,城市垃圾产量呈现出急剧增长的态势。传统的垃圾处理模式,如简易堆放或填埋,已经暴......
  • 一文夯实垃圾收集的理论基础
    如何判断一个引用是否存活引用计数法给对象中添加一个引用计数器,每当有一个地方引用它,计数器就加1;当引用失效,计数器就减1;任何时候计数器为0的对象就是不可能再被使用的。优点:可即刻回收垃圾,当对象计数为0时,会立刻回收;弊端:循环引用时,两个对象的计数都为1,导致两个对象都无法......
  • 垃圾堆积视频监测
    垃圾堆积视频监测利用现场已有的监控摄像头,垃圾堆积视频监测通过人工智能AI视觉分析技术自动识别小区垃圾桶、街道、马路、路口是否有垃圾堆放、垃圾桶满溢等情况。一旦检测到垃圾堆放、垃圾桶满溢、垃圾暴露等情况,系统会自动截图并发出告警消息,提醒管理人员及时处理。与传统的人......