GC面临的困境,JVM是如何解决跨代引用的?

news2024/10/7 12:27:18

本文已收录至GitHub,推荐阅读 👉 Java随想录

微信公众号:Java随想录

原创不易,注重版权。转载请注明原作者和原文链接

文章目录

    • 跨代引用问题
    • 记忆集
    • 卡表
    • 写屏障
    • 写屏障的伪共享问题

前面我们讲了可达性分析和根节点枚举,介绍完了GC的前置工作,下面开始讲GC的工作过程。

然而在GC开始工作之前,有一个不得不解决的问题摆在我们面前:「跨代引用问题」。

本篇文章就来聊聊什么是跨代引用问题,以及JVM是如何解决跨代引用问题的。

跨代引用问题

跨代引用是指新生代中存在对老年代对象的引用,或者老年代中存在对新生代的引用。

为什么说这是一个问题呢?请看下图。

假如现在要进行一次只局限于新生代区域的YGC,但新生代中的对象是完全有可能被老年代所引用的,为了找到新生代中的存活对象,不得不遍历整个老年代来确保可达性分析结果的正确性。

首先,我们得明确一点,跨代引用是极少的,这很重要。

举个例子说明:如果某个新生代对象存在跨代引用,由于老年代对象难以消亡,该引用会使得新生代对象在收集时同样得以存活,进而在年龄增长之后晋升到老年代中,这时跨代引用也随即被消除了。

这简直就是原子弹炸鸟,起重机吊鸡毛。因为跨代引用是极少的,为了找出那么一点点跨代引用,却得遍历整个老年代!

而JVM里GC回收无疑是非常频繁的动作,如果每次都这么搞,性能肯定吃不消,无疑会为内存回收带来很大的性能负担。

别慌,JVM的设计者已经考虑到了这个场景,并想到了解决办法,那就是使用一种叫做:「记忆集(Remembered Set)」的数据结构。

记忆集

记忆集位于新生代中,是一种用于记录从非收集区域指向收集区域的指针集合的抽象数据结构。用以避免把整个老年代加进GC Roots扫描范围。

记忆集的作用和我们之前讲的OopMap很相似,维护了类似一种映射表的关系,避免了全局扫描,本质是用空间换时间。

此后当发生YGC时,只要把记忆集加进来一起扫描,就能知道新生代对象被老年代引用的情况,而不必扫描整个老年代!

虽然说增加了维护记忆集的成本,但比起收集时扫描整个老年代来说这波还是血赚!

上面不知道大家有没有留意我的说辞:「抽象数据结构」。意思就是说记忆集是一种逻辑上的概念,并没有规定具体的实现,类似方法区。

在HotSpot中,采用卡表去实现记忆集。可以把记忆集和卡表的关系理解为Map跟HashMap。

卡表

卡表可以理解为是记忆集的具体实现,英文叫:Card Table。

垃圾收集器只需要通过记忆集判断出某一块非收集区域是否存在有指向了收集区域的指针就可以了,并不需要了解这些跨代指针的全部细节。

那设计者在实现记忆集的时候,便可以选择更为粗犷的记录粒度来节省记忆集的存储和维护成本,下面列举了一些可供选择(当然也可以选择这个范围以外的)的记录精度:

其中,第三种「卡精度」所指的就是「卡表」的方式去实现记忆集 ,这也是目前最常用的一种记忆集实现形式,HotSpot采用的就是卡表。

在HotSpot虚拟机里面,卡表采用的是字节数组的形式。以下这行代码是HotSpot默认的卡表标记逻辑 :

CARD_TABLE [this address >> 9] = 0;

字节数组CARD_TABLE的每一个元素都对应着其标识的内存区域中一块特定大小的内存块,这个内存块被称作「卡页(Card Page)」。

一般来说,卡页大小都是以2的N次幂的字节数,通过上面代码可以看出HotSpot中使用的卡页是2的9次幂,即512字节。

意味着如果卡表标识内存区域的起始地址是0x0000的话,数组CARD_TABLE的第0、1、2号元素,分别对应了地址范围为0x0000~0x01FF、0x0200~0x03FF、0x0400~0x05FF的卡页内存块 ,如图所示:

一个卡页的内存中通常包含不止一个对象,只要卡页内有一个(或更多)对象的字段存在着跨代指针,那就将对应卡表的数组元素的值标识为1,称为这个元素变脏(Dirty),没有则标识为0。

简单来说,就是卡页的字节数组只有0和1两种状态,1表示哪些内存区域存在跨代指针,那么只要把1的加入GC Roots中一并扫描,就能知道哪些进行跨代引用了,这样就不用挨个去扫描了。

OK,到了这步我们的思路就清晰了。

可以把老年代划分为一个个内存区域,每块内存区域分别对应卡表的元素,然后把卡表中变脏的元素,直接加入GC Roots中一并扫描,跨代引用问题就迎刃而解了。

如图,对象A在老年代 0x0000~0x01FF 内存区域被引用,那只要把对应的卡表标记为1,YGC的时候扫描卡表,就能知道对象A被老年代哪块内存区域引用了。

but,我们还剩下一个问题,卡表元素如何维护?类似问题OopMap也遇到过。

卡表元素如何维护?何时变脏?谁来把它们变脏?

HotSpot解决的办法是使用写屏障。

写屏障

先来解决何时变脏的问题,这个问题很简单,即其他分代区域中对象引用了本区域对象时,其对应的卡表元素就应该变脏,变脏时间点原则上应该发生在引用类型字段赋值的那一刻

但问题是如何变脏,即如何在对象赋值的那一刻去更新维护卡表。

在HotSpot虚拟机里是通过「写屏障(Write Barrier)」解决的。

注意:这里提到的 写屏障 和 volatile 的写屏障不是一回事。

写屏障可以看作在虚拟机层面对「引用类型字段赋值」这个动作的AOP切面,在引用对象赋值时会产生一个环形(Around)通知。用过Spring的弟兄们对AOP肯定不陌生。

在赋值前的部分的写屏障叫作「写前屏障(Pre-Write Barrier)」,在赋值后的则叫作「写后屏障(Post-Write Barrier)」。

HotSpot虚拟机的许多收集器中都有使用到写屏障,但直至G1收集器出现之前,其他收集器都只用到了写后屏障。

应用写屏障后,虚拟机就会为所有赋值操作生成相应的指令,一旦收集器在写屏障中增加了更新卡表操作,无论更新的是不是老年代对新生代对象的引用,每次只要对引用进行更新,就会产生额外的开销,不过这个开销与YGC时扫描整个老年代的代价相比还是低得多的。

当引入一个解决方案的时候,随之而来的可能还有其他问题。卡表在高并发场景下还面临着「伪共享(False Sharing)」问题。

写屏障的伪共享问题

伪共享是处理并发底层细节时一种经常需要考虑的问题,号称并发的「隐形杀手」。

现代中央处理器的缓存系统中是以缓存行(Cache Line)为单位存储的,当多线程修改互相独立的变量时,如果这些变量恰好共享同一个缓存行,就会彼此影响(写回、无效化或者同步)而导致性能降低。

core1 更新 A,同时 core2 更新 B,由于数据的读取和更新是以「缓存行」为单位的,这就意味着当这两件事同时发生时,就产生了竞争,导致 core1 和 core2 有可能需要重新刷新自己的数据(缓存行被对方更新了),最终导致系统的性能大打折扣,这就是伪共享问题。

为了避免伪共享问题,一种简单的解决方案是不采用无条件的写屏障,而是先检查卡表标记,只有当该卡表元素未被标记过时才将其标记为变脏。

即将卡表更新的逻辑变为以下代码所示:

if (CARD_TABLE [this address >> 9] != 0)
CARD_TABLE [this address >> 9] = 0;

相当于说其实就是多了一个「if 判断条件」。

在JDK 7之后,HotSpot虚拟机增加了一个新的参数「-XX:+UseCondCardMark」,此参数默认是关闭的,用来决定是否开启卡表更新的条件判断。

开启会增加一次额外判断的开销,但能够避免伪共享问题,两者各有性能损耗,是否打开要根据应用实际运行情况来进行测试权衡。

看到这,本篇文章就结束啦,这章讲了跨代引用和记忆集。

GC收集还有很多是需要我们去搞清楚的。知道的越多,不知道的越多,这只是个开端,一起期待下篇的「三色标记算法」吧。


感谢阅读,如果本篇文章有任何错误和建议,欢迎给我留言指正。

老铁们,关注我的微信公众号「Java 随想录」,专注分享Java技术干货,文章持续更新,可以关注公众号第一时间阅读。

一起交流学习,期待与你共同进步!

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

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

相关文章

你真的了解四种内部类吗

作者简介: zoro-1,目前大一,正在学习Java,数据结构等 作者主页: zoro-1的主页 欢迎大家点赞 👍 收藏 ⭐ 加关注哦!💖💖 内部类 实例内部类注意事项 静态内部类注意事项 局…

联发科雄起,生成式AI将可在手机本地运行,天玑9300“大放异彩”

据报道,高通和联发科都计划在即将推出的旗舰手机上集成设备端生成式人工智能功能。这意味着用户将能够在手机上使用先进的语言模型技术。 高通选择与Meta合作,将其最新的语言模型Llama 2(LLM)引入他们的设备,而联发科则…

TCP最大连接数问题总结

最大TCP连接数量限制有:可用端口号数量、文件描述符数量、线程、内存、CPU等。每个TCP连接都需要以下资源,如图所示: 1、可用端口号限制 Q:一台主机可以有多少端口号?端口号与TCP连接?是否能修改&#x…

编程锦囊妙计——快速创建本地Mock服务

点击上方👆蓝色“Agilean”,发现更多精彩。 前情提要 在本系列上一篇文章《全文干货:打破前后端数据传递鸿沟,高效联调秘笈》中我们分享了使用Zod这一运行时类型校验库来对后端服务响应结果进行验证达到增加项目质量的方式。 这次…

TypeError: div() got an unexpected keyword argument ‘rounding_mode’

报错代码:torch.div(…, …, rounding_mode“floor”) mask_idx torch.div(idx, self.instance_classes, rounding_mode“floor”) 报错原因: rounding_mode在torch1.8才引入 https://github.com/mit-han-lab/torchsparse/pull/126 torch.div()方法的…

生信豆芽菜-信号转导通路相关评分的计算

网址:http://www.sxdyc.com/gradeSigna 1、数据准备 表达谱数据,行为基因,列为样本 2、提交后,等待运行成功即可下载 当然,如果不清楚数据是什么样的,可以选择下载我们的示例数据,也可以关…

跨境出海:如何轻松应对多账号管理

在如今的跨境电商时代,成功经营一个线上店铺不再仅仅需要商品和服务,还需要精通广告投放、营销策略等多个领域。 然而,老练的电商从业者知道,如果不重视平台账号的管理方法,可能会导致店铺或营销账号被关联&#xff0…

NO.06 自定义映射resultMap

1、前言 在之前的博客中,实体类的属性名和数据库表的字段名是一致的,因此能正确地查询出所需要的数据。当实体类的属性名与数据库表的字段名不一致时,会导致查询出来的数据为空指针。要解决这个问题就需要使用resultMap自定义映射。 使用的…

前端常用插件推荐

1.MD格式文件 我们经常会遇到md格式文件的编辑和预览,推荐两个插件: uiw/react-markdown-editor,uiw/react-markdown-preview,不通语言应该有对应的版本,这里是react为例子,也是用过其他的组件&#xff0…

一键实现 Oracle 数据整库同步至 Apache Doris

在实时数据仓库建设或迁移的过程中,用户必须考虑如何高效便捷将关系数据库数据同步到实时数仓中来,Apache Doris 用户也面临这样的挑战。而对于从 Oracle 到 Doris 的数据同步,通常会用到以下两种常见的同步方式: OGG/XStream/Lo…

JAVA基础-02实例变量和类变量区别

0x00 类变量 类变量(Class Variables),也称为静态变量(Static Variables),是在类级别声明的变量,与类的所有实例共享相同的值。与实例变量不同,类变量不属于对象的特定实例&#xf…

韦东山lcd驱动

RGB 接口的 TFT-LCD 驱动 芯片集成了显存(ddr、SDRM)和控制器 一个像素点的颜色使用 24位表示,这样的话硬件对应数据引脚有24个 LCD硬件模型 RGB 传输数据 HSYNC 行同步 VSYNC 帧同步 DE 使能有效 declock 移动一个像素(频率…

简述docker的网络模式

Docker 提供了多种网络模式,用于控制容器之间以及容器与主机之间的网络通信。以下是 Docker 的一些常见网络模式 briage模式: docker容器启动时默认就是该模式,在该模式下,docker容器会连接到一个名为docker0的虚拟以太网桥上,通…

【pytorch】Unfold和Fold的互逆操作

1. 参数定义 Unfold https://pytorch.org/docs/stable/generated/torch.nn.Unfold.html#torch.nn.Unfold Fold https://pytorch.org/docs/stable/generated/torch.nn.Fold.html#torch.nn.Fold 注意:参数当中的padding是在四周边补零,而当fold后的尺寸…

AMBA总线协议(8)——AHB(六):分割传输

一、前言 在之前的文章中,我们重点介绍了AHB传输的仲裁,首先介绍了仲裁相关的信号,然后分别介绍了请求总线访问,授权总线访问,猝发提前终止,锁定传输和默认主机总线,在本文中我们将继续介绍AHB的…

生信豆芽菜-单样本GSEA分析

网址:http://www.sxdyc.com/gradeSsgsea 1、数据准备 第一个文件:表达谱数据 第二个文件:功能基因集 2、提交后,等待运行成功即可下载 当然,如果不清楚数据是什么样的,可以选择下载我们的示例数据&a…

【报错解决】:DataGrip连接Mysql报08S01解决方法

DataGrip连接Mysql报08S01解决方案 报错 [08S01]Communications link failureThe last packet sent successfully to the server was 0 milliseconds ago. The driver has not received any packets from the server. No appropriate protocol (protocol is disabled or ciph…

Ubuntu Touch OTA-2 推出,支持 Fairphone 3 和 F(x)tec Pro1 X

导读UBports 基金会近日宣布为基于 Ubuntu 20.04 LTS (Focal Fossa) 的 Ubuntu Touch 移动操作系统发布并全面提供 OTA-2 软件更新。 Ubuntu Touch OTA-2 在首次 OTA 更新整整四个月后发布,支持新设备,包括 Fairphone 3、F(x)tec Pro1 X 和 Vollaphone X…

跌倒检测,基于YOLOV8S,只依赖OPENCV,支持C++/PYTHON

跌倒检测,只依赖OPENCV,支持C/PYTHON YOLOV8S检测原理,很多CSDN博客已经介绍了,只需要标记数据,然后训练,转换成ONNX模型,然后OPENCV的DNN模块调用,支持C/PYTHON

MySQL数据库面试题汇总

MySQL数据库 1.什么是数据库? 数据库(Database)是“按照数据结构来组织、存储和管理数据的仓库”。是一个长期存储在计算机内的、有组织的、可共享的、统一管理的大量数据的集合。(简而言之,用来存储大量数据的&…