剖析G1 垃圾回收器

news2024/11/17 3:33:06

简单回顾

        在Java当中,程序员在编写代码的时候只需要创建对象,从来不需要考虑将对象进行释放,这是因为Java中对象的垃圾回收全部由JVM替你完成了(所有的岁月静好都不过是有人替你负重前行)。

        而JVM的垃圾回收由垃圾回收器来负责,在JDK的不断更新迭代过程中,JVM的垃圾回收器也经历了Serial 垃圾收集器、Serial Old 垃圾收集器、ParNew 垃圾收集器、Parallel Old垃圾收集器、CMS垃圾收集器,以及目前最流行的G1垃圾回收器(也就是本文重点要讲的)。

G1的诞生与发展

        G1垃圾回收器最早在2004年发表论文提出(Garbage-First Garbage collection,doi:10.1145/1029873.1029879),在JDK6 中首次被应用,在JDK7中被正式支持,而到了2019年发布的JDK9中,G1垃圾收集器已经作为了官方默认的垃圾收集器,取代了之前的CMS。

 

G1有什么厉害的地方

        先介绍两个词:硬实时性和软实时性。

        硬实时性(hard real-time):每次处理的时间都不能超过最后期限,比如医疗机器人控制系统、航空管制系统。

        软实时性(soft real-time):稍微超出几次最后期限也没有什么问题的系统,例如网络银行系统。

        G1最大的特点就是非常重视高吞吐量与软实时性的最佳平衡,它让用户来设定期望最大暂停时间(Stop the word),也就是在垃圾回收时停止所有用户线程的时间,G1垃圾收集器可以预测下次 GC 会导致应用程序暂停多长时间。然后根据预测出的结果,G1会通过延迟执行GC、拆分 GC 目标对象等手段来尽量满足用户设置的期望最大暂停时间,默认的暂停目标是 200ms。你想让GC时暂停多久,它就能尽量的满足你。

G1垃圾收集器的应用场景最好需要包含以下特性(满足这些特性的话,则可能更适合G1出马,否则可能其他GC更合适):

  • 堆内存大小超过10G,且存活对象占用比例超过50%

  • 对象分配和晋升速率可能随时间有显著变化

  • 堆中存在大量碎片

  • 预测的最大停顿时间不超过几百毫秒

G1有什么特别牛逼的地方呢?

        首先,以往的 GC 都是尽可能缩短最大暂停时间,缩短最大暂停时间很容易导致吞吐量下降。当然我们肯定希望是暂停时间越短约好呀,但是暂停时间过短很可能会导致频繁发生GC,从而把CPU全部都打满了,也就是吞吐量降低。所以并不是暂停时间越短机器性能越好。另外以往的 GC 无法预测暂停时间,GC 时可能会使应用程序长时间暂停的风险。G1的目的就是高效地实现软实时性,能够让用户设置期望暂停时间。在确保吞吐量比以往的 GC 更好的前提下,实现了软实时性。最大程度利用服务器上多处理器的优势,而且在处理巨大的堆时,也不会降低 GC 的性能。

G1垃圾收集模型

堆内存划分

        G1垃圾收集器采取了和之前所有的垃圾收集器完全不一样的思路,可以说是一个开拓者。别人都在研究如何让马车能够把车拉的更快(改良马车结构,马车上多绑几匹马),G1相当于发明出来了蒸汽机来替代马车。

 

        G1垃圾收集器开创了面向局部收集的设计思路和基于Region的内存布局形式。G1不再坚持固定大小以及固定数量的分代区域划分,而将内存结构划分成如图所示大小相等的块状区域,称为region,region是内存分配和内存回收的最小单位,每个Region都可以成为 Eden空间、Survivor空间、老年代空间。region的大小可以由用户进行调整,但是内部会将用户设置的值向上调整为 2 的指数幂来作为区域的大小。

        图中绿色区域指代的是新生代中的伊甸园区(Eden),蓝色区域指代的是老年代(old generation),标注为S的橙色区域的为Suvivor Region,标注为H的蓝色区域为Humoungous Region(巨型对象,也属于老年代),灰色的区域为空闲区。我们从图中对象占用的内存都不是连续的,并且任何一个区域都没有特定的年代划分,可以将它分配成新生代也可以分配为老年代。由于分区的原因,G1可以只选取部分区域进行内存回收,这样缩小了回收的范围,因此对于全局停顿情况的发生也能得到较好的控制。这是G1和其他收集器在堆内存结构上的最大差别,也是为什么G1更适合处理堆中存在大量碎片场景的原因。

 

卡表与卡页

        JVM将堆内存划分为了2次幂大小的卡页(Card Page),使用卡表来记录整个内存的状态。默认情况下,卡页的大小为512B。卡表(Card Table)是由1B组成的数组,卡表里的元素称为卡片(Card),每个卡片对应堆内存中的一个卡页,卡表中的卡片与卡页的映射关系:卡片数组位置为堆内存地址除卡页大小(向下取整)。在堆大小是 1GB 时,卡表大小为 2MB。

 

        堆中对象所对应的卡片在卡表的索引值 = (对象的地址 - 堆的头部地址) / 512

        因为卡片的大小是 1B,所有可以表示其对应卡页所处的很多状态,在后面只涉及到其中的两种:净卡片和脏卡片。

记忆集(RSet)

        记忆集英文全称为RemerberSet,简称RSet,是一种抽象概念。其核心思想是通过卡表,记录对象在不同代际间的引用关系,加速垃圾回收的速度。

        JVM会通过可达性分析算法标记存活对象来帮助进行垃圾回收,不过在分代GC中,新生代和老年代处于不同的回收阶段,如果仅仅只需要回收新生代,却标记了老年代的对象,那么这无疑是不必要的。但是如果只回收新生带的时候,新生代的对象有被老年代引用的情况,也就是出现跨区引用时,只扫描新生代显然是不行的。所以JVM设计了RSet这样的玩意来避免这种现象,将跨带引用的信息都记录到对应的记忆集中,使其即便不扫描全部对象,也可以查到待回收对象所在分区被其它分区引用的情况。

        每个Region中都会开辟一小块的区域作为记忆集的存储位置,记忆集的结构为一个Hash表,Key为引用本分区的其它分区的地址,Value是卡片(Card)在卡表的索引值,即分区中的哪些卡页引用了本分区。

 

例如在下图中,区域 B 中的对象 b 引用了区域 A 中的对象 a。因为对象 b 不是区域 A 中的对象,所以必须记录这个引用关系。在记忆集合 A 中,以区域 B 的地址为键记录了卡片的索引 2048(对象 b 对应的卡片索引),此时对象 b 对对象 a 的引用被准确记录了下来。

 

那么是否是所有的引用关系都需要记录到记忆集中呢?答案不是的,分为以下几种情况来进行讨论。

无需记录引用关系:

  • Region内部间的引用:无需记录引用关系,Region是内存分配和回收的最小单位,要么都回收,要么都不回收,所以不需要进行记录。

  • 新生代分区到新生代分区的引用:无需记录引用关系,无论哪种类型的回收,新生代都会全量回收。

  • 新生代分区到老年代分区的引用:无需记录引用关系,对于YoungGC来说,针对的新生代,则无需关心;对于混合GC来说,会使用新生代分区作为根,那么遍历所有新生代分区自然能找到老年代;对于FullGC来说,所有分区都会被清理,无需关心引用关系。

需要记录引用关系:

  • 老年代分区到新生代分区的引用:需要记录引用关系,新生代回收时,有两种根,一种是栈空间/全局变量的引用,另一种便是老年代到新生代的引用

  • 老年代分区到老年代分区的引用:需要记录引用关系,混合回收时可能只回收部分Region,需要记录引用关系,快速找到活跃对象

写屏障

记忆集的主要作用是记录跨Region的引用关系,为保证其正确性,那么当引用关系变化时,我们需要及时更新记忆集,而记忆集写屏障则负责对记忆集进行更新。

每个应用程序线程都持有一个转移专用记忆集合日志的缓冲区,其中存放的是卡片索引的数组。当对象的域被修改时,写屏障就会感知,并会将对象 所对应的卡片索引添加到转移专用记忆集合日志中。

主要步骤:

  1. 从转移专用记忆集合日志的集合中取出转移专用记忆集合日志,从头开始扫描

  2. 将卡片变为净卡片

  3. 检查卡片所对应存储空间内的所有对象的域

  4. 向域中地址所指向的区域的记忆集合中添加卡片

G1 垃圾回收过程

        G1的GC主要分为Young GC和Mixed GC两种模式,有些特殊场景可能会发生Full GC。不过如果按照垃圾回收的阶段来划分的话,G1的垃圾回收过程只包含两个阶段,Young-Only和Space Relcaimation阶段。

        在Young Only阶段,G1只会回收新生代内存,即新生代回收,在Space Reclamation阶段,G1除了会全量回收新生代内存,还会回收老年代区域,即混合回收。Full GC是一种特殊的兜底回收逻辑,此处不考虑进来。所以G1的垃圾回收其实不是我们所想的Young GC和Mixed GC穿插进行,而是Young GC 持续一段时间,Mixed GC 再持续一段时间。

        图中的圆圈表示G1回收过程中的暂停:蓝色圆圈表示Young-only GC导致的暂停,红色圆圈表示Mixed GC导致的暂停,黄色圆圈表示有并发标记导致的暂停。

全局并发标记

并发标记的时机是在Young GC后时,只有达到InitiatingHeapOccupancyPercent阈值后,才会触发并发标记。InitiatingHeapOccupancyPercent默认值是45。

整个过程分为四个步骤:

初始标记:标记处所有跟GC Roots直接关联的对象,这一阶段STW,并且耗时很短,主要是修改TAMS指针的值,让下一阶段分配对象能够使用Region内存。

并发标记:从GC Roots对堆中的对象进行可达性分析, 找出存活的对象,并发标记阶段产生的新的引用会被SATB的写屏障记录下来,并且还会定期更新和处理SATB局部缓存表的信息和记录,如果发现某一个Region中所有都是垃圾,那么就直接进行回收。

重新标记:标记在并发期间因为程序运作而改变的引用对象。

清除:进行价值衡量,回收最优价值的Region区。

转移存活对象

在并发标记完成后,G1能筛选出所有候选的回收集,并根据用户定义的期望最大停顿时间,筛选本次转移真正的回收集(CSet),标记回收集中的存活对象,将这些对象转移,完成垃圾回收。

参数介绍

参数

说明

-XX:+UseG1GC

使用 G1 收集器

-XX:G1HeapRegionSize=n

设置 G1区域Region的大小。范围从1 MB 到32MB之间,目标是根据最小的 Java 堆大小划分出大约2048个区域。

-XX:MaxGCPauseMillis=200

设置最长暂停时间目标值,默认是200毫秒

-XX:G1NewSizePercent=5

设置年轻代最小值占总堆的百分比,默认值是5%

-XX:G1MaxNewSizePercent=60

设置年轻代最大值占总堆的百分比,默认值是java堆的60%

-XX:ParallelGCThreads=n

设置STW并行工作的GC线程数,一般推荐设置该值为逻辑处理器的数量,最大是8;如果逻辑处理器大于8,则取逻辑处理器数量的5/8;这适用于大多数情况,除非是较大的SPARC系统,其中的n值可以是逻辑处理器的5/16

-XX:ConcGCThreads=n

并发标记阶段,并发执行的线程数,一般n值为并行垃圾回收线程数(ParallelGCThreads)的1/4左右

-XX:InitiatingHeapOccupancyPercent=45

设置触发全局并发标记周期的Java堆内存占用率阈值,默认占用率阈值是整个Java堆的45%

-XX:G1MixedGCLiveThresholdPercent=85

老年代Region中存活对象的占比,只有当占比小于此参数的Old Region,才会被选入CSet。这个值越大,说明允许回收的Region中的存活对象越多,可回收的空间就越少,gc效果就越不明显

-XX:G1HeapWastePercent=5

设置G1中愿意浪费的堆的百分比,如果可回收region的占比小于该值,G1不会启动Mixed GC,默认值10%,主要用来控制Mixed GC的触发时机。在global concurrent marking结束之后,我们可以知道老年代regions中有多少空间要被回收,在每次YGC之后和再次Mixed GC之前,会检查垃圾占比是否达到此参数,只有达到了,下次才会触发Mixed GC。

-XX:G1MixedGCCountTarget=8

一次全局并发标记后,最多执行Mixed GC的次数,次数越多,单次回收的老年代的Region个数就越少,暂停也就越短

-XX:G1OldCSetRegionThresholdPercent=10

一次Mixed GC过程中老年代Region内存最多能被选入CSet中的占比

-XX:G1ReservePercent=10

设置作为空闲空间的预留内存百分比,用来降低目标空间溢出的风险,默认是10%,一般增加或减少百分比时,需要确保也对java堆调整相同的量。

实例参考

下面是一台部署线上服务的4核10G的服务器的GC情况,其使用的垃圾回收器为G1:

 如图可得该实例在最近两小时共产生16次GC,平均每次间隔七八分钟。

 

        每次GC耗时没有超过50ms,因此每分钟的吞吐量也超过了99%,符合MaxGCPauseMillis=200这个配置,所有的停顿时间都没有超出目标值。

         堆内存大约在到达4G的时候开始被回收,符合InitiatingHeapOccupancyPercent=40这个配置,在堆内存达到10*40%=4G的时候开始触发标记周期,然后进行内存回收工作.

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

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

相关文章

刷题记录:牛客NC200179Colorful Tree 奇奇怪怪的dfs序

传送门:牛客 题目描述: A tree structure with some colors associated with its vertices and a sequence of commands on it are given. A command is either an update operation or a query on the tree. Each of the update operations changes the color of a specifi…

论文阅读 - End-to-End Wireframe Parsing

文章目录1 概述2 L-CNN2.1 整体架构2.2 backbone2.3 juction proposal module2.4 line sample module2.5 line verificatoin module3 评价指标参考资料1 概述 本文是ICCV2019的一篇论文,核心是提出了一种简单的end-to-end的two-stage的检测图像中线段的方法。同时&…

192、【动态规划】leetcode ——64. 最小路径和:回溯法+动态规划(C++版本)

题目描述 原题链接&#xff1a;64. 最小路径和 解题思路 &#xff08;1&#xff09;回溯法 分别向右或下进行探查 class Solution { public:int res INT_MAX;void backtracking(vector<vector<int>>& grid, int x, int y, int pathSum) {// 超出边界&…

高可用 - 08 Keepalived集群中Master和Backup角色选举策略

文章目录概述实例说明“weight”值为正数“weight”值为负数总结概述 在Keepalived集群中&#xff0c;其实并没有严格意义上的主、备节点&#xff0c;虽然可以在Keepalived配置文件中设置“state”选项为“MASTER”状态&#xff0c;但是这并不意味着此节点一直就是Master角色。…

Python实现人脸识别,进行视频跟踪打码,羞羞的画面统统打上马赛克

哈喽兄弟们&#xff0c;我是轻松~ 今天我们来实现用Python自动对视频打马赛克前言准备工作代码实战效果展示最后前言 事情是这样的&#xff0c;昨天去表弟家&#xff0c;用了下他的电脑&#xff0c;不小心点到了他硬盘里隐藏的秘密&#xff0c;本来我只需要用几分钟电脑的&…

第一章初识Linux

文章目录Linux简介LInux的应用领域Linux OS和各种发行版的关系Linux和Unix的关系Linux相关环境配置图解VM和Linux的关系Linux自定义分三个区VMware网络连接的三种模式桥接模式NAT模式主机模式VMware快照功能Linux的操作方式Linux的目录结构各种Linux发行版本的常见目录注意事项…

GO进阶(5) 垃圾回收机制

一、前言 1、垃圾回收背景 编程语言通常会使用手动和自动两种方式管理内存&#xff0c;C、C 以及 Rust 等编程语言使用手动的方式管理内存&#xff0c;工程师需要主动申请或者释放内存&#xff1b;而 Python、Ruby、Java 和 Go 等语言使用自动的内存管理系统&#xff0c;一般都…

Java八股——wait、sleep与park

sleep()、wait()、park()都可以使线程进入等待状态&#xff0c;但是3种方式在使用上和功能上都有些不同。 共同点: wait()&#xff0c;wait(long)和sleep(long)的效果都是让当前线程暂时放弃CPU的使用权&#xff0c;进入阻塞状态它们都可以被打断唤醒都是native方法执行sleep…

java四种线程池(基本使用)

标题java四种线程池及使用示例 1、线程工厂 1、我们先来写ThreadFactory&#xff0c;在创建线程池时候可以传入自定义的线程工厂&#xff0c;线程工厂说白了就是用来定制线程的一些属性&#xff1a;名字、优先级、是否为守护线程。直接看代码即可。 当然创建线程池的时候可以…

控制台运行java

控制台执行java 新建java代码 新建一个记事本文件&#xff0c;将文件名改为HelloWorld.java&#xff0c;注意&#xff1a;后缀是.java。 若没有显示文件后缀&#xff0c;可以在资源管理器打开显示后缀&#xff0c;然后再次修改文件名&#xff0c;一定要修改成文件类型是java…

缺陷及缺陷管理

今日目标能够说出缺陷的判定标准能够说出描述缺陷的6大核心内容能够描述缺陷状态、严重程度、优先级的作用能够按照提供的缺陷模版完成一个缺陷的提交能够说出缺陷的跟踪流程能够在禅道中提交测试用例能够在禅道中提交缺陷1. 缺陷1.1 缺陷的定义&#xff08;重点&#xff09;产…

Python3.8.8-Django3.2-Redis-连接池-数据类型-字符串-list-hashmap-命令行操作

文章目录1.认识Redis1.1.优点1.2.缺点2.在Django中Redis的连接3.Redis的基础用法3.1.hashmap结构3.2.list结构4.命令行查看数据库5.作者答疑1.认识Redis Remote DIctionary Server(Redis) 是一个key-value 存储系统&#xff0c;是跨平台的非关系型数据库。是一个开源的使用 AN…

Linux入门篇-Linux目录结构

简介 简单介绍Linux目录。 Linux的⽬录结构 “/”表示根⽬录&#xff0c;根⽬录是Linux⽬录结构中的最顶级的⽬录&#xff0c;类似于windows的C:\ D:\ /boot⽬录&#xff1a;存放的是系统的启动配置⽂件和内核⽂件 /dev⽬录&#xff1a;存放的是Linux的设备⽂件 /etc⽬录&…

Nacos新手详细知识大全

官网&#xff1a;Spring Cloud Alibaba一、Nacos快速入门1.1 服务注册到Nacos1.1.1 配置**引入com.alibaba.cloud&#xff0c;**以后的版本就不用操心了<!-- com.alibaba.cloud--><!-- com.alibaba.cloud--><dependency><groupId>com.alibaba.cloud<…

SpringCloud之 LoadBalancer负载均衡

文章目录LoadBalancer 负载均衡一、LoadBalanced 负载均衡二、自定义负载均衡三、OpenFeign 实现负载均衡①添加依赖②启动类添加 EnableFeignClients③创建客户端接口 UserClient④service业务中调用客户端接口提示&#xff1a;以下是本篇文章正文内容&#xff0c;SpringCloud…

pv和pvc

一、PV和PVC详解当前&#xff0c;存储的方式和种类有很多&#xff0c;并且各种存储的参数也需要非常专业的技术人员才能够了解。在Kubernetes集群中&#xff0c;放了方便我们的使用和管理&#xff0c;Kubernetes提出了PV和PVC的概念&#xff0c;这样Kubernetes集群的管理人员就…

【Python】torch.einsum()解析

【Python】torch.einsum()解析 文章目录【Python】torch.einsum()解析1. 介绍2. 示例2.1 Pytorch矩阵乘法2.2 Numpy高阶张量3. 参考1. 介绍 爱因斯坦简记法&#xff1a;是一种由爱因斯坦提出的&#xff0c;对向量、矩阵、张量的求和运算 ∑\sum∑ 的求和简记法。在该简记法当中…

链表面试题

链表面试题1. 删除链表中等于给定值 val 的所有结点。2. 反转一个单链表。3. 给定一个带有头结点 head 的非空单链表&#xff0c;返回链表的中间结点。如果有两个中间结点&#xff0c;则返回第二个中间结点。4. 输入一个链表&#xff0c;输出该链表中倒数第k个结点。5. 将两个有…

延时任务的四种实现方式

什么是延迟任务&#xff1f;顾明思议&#xff0c;我们把需要延迟执行的任务叫做延迟任务。延迟任务的使用场景有以下这些&#xff1a;红包 24 小时未被查收&#xff0c;需要延迟执退还业务&#xff1b;每个月账单日&#xff0c;需要给用户发送当月的对账单&#xff1b;订单下单…

计算机网络之MAC和IP地址

MAC地址 在局域网中&#xff0c;硬件地址&#xff0c;又称为物理地址或MAC地址。 目前现在的局域网中实际上使用的都是6字节的MAC地址&#xff0c;所以每一个以太网设备都具有唯一的MAC地址。 MAC地址的格式 假设传输使用的是IP数据&#xff0c;V2的MAC帧较为简单&#xff…