当前位置: 首页 > news >正文

JVM:如果是你,你如何解决跨代引用的问题?(记忆集和卡集)

这部分内容主要是为了稍后介绍各款垃圾收集器时做前置知识铺垫,如果对这部分内容感到枯燥或者疑惑,可以先放下看,等后续遇到要使用它们的实际场景、实际问题时再结合问题,再回来阅读和理解。

记忆集和卡集

前面在分代收集理论那一节稍微讲到对象不是孤立的,对象之间存在跨代引用。如果还是不大明白,看我下面这个例子:老年代引用年轻代

public class ClassRoomLocalCache {// 静态变量 map,以及 map 中引用的 ClassRoom 类型变量大概熬过默认的 15 次垃圾收集,都会晋升到老年代private static final Map<String,ClassRoom> map = new ConcurrentHashMap<>();public static void addStudent(String classRoomId) {// ......// 运行时,新初始化了一个对象 Student ,对象实例假设在年轻代分配内存// 老年代引用年轻代map.get(classRoomId).getUsers().add(new Student("127", "李华", 22));}@Getterprivate static class ClassRoom {private String id;private String name;private Integer num;private List<Student> users;}@AllArgsConstructorprivate static class Student {private String id;private String name;private Integer age;}}

再回一下前面提到的经验法则三,为什么说跨代引用相对于同代引用来说仅占极少数?

因为存在互相引用关系的两个对象,是应该倾向于同时生存或者同时消亡的。例如上面程序中的这个例子(稍微结合例子解释下)

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

其实上面说的只是一个经验法则或者说是理论,实际上并没有解决当需要在年轻代gc 时因可能存在跨在引用,为了找出该区域中的存活对象,不得不在固定的GC Roots之外,再额外遍历整个老年代中所有对象来确保可达性分析结果的正确性

事实上并不只是新生代、老年代之间才有跨代引用的问题,所有涉及部分区域收集(Partial GC)行为的垃圾收集器(G1、ZGC)都会面临相同的问题。

可以想一下,如果是你,你会怎么设计解决这个问题?

由于跨代引用占极少数,我想最简单的实现的就是维护一个对象数组,每当有老年代对象引用年轻代对象就把老年代对象存储到这个数组中,然后在年轻代收集时,把这个数组的对象一并加入到gc roots 中去进行扫描。

实际上,JVM 的设计者的思路也大概是这样,就是在收集区域(可以理是新生代)开辟一块小内存维护一个数据结构,这个抽象数据结构用于记录从非收集区域指向收集区域的指针集合,他们称为记忆集。

然而JVM 设计者并没有直接使用如上最简单的实现方案。这种记录全部含跨代引用对象的实现方案,无论是空间占用还是维护成本都相当高昂。而在垃圾收集的场景中,收集器只需要通过记忆集判断出某一块非收集区域是否存在有指向了收集区域的指针就可以了,并不需要了解这些跨代指针的全部细节。那设计者在实现记忆集的时候,他们选择更为粗犷的记录粒度来节省记忆集的存储和维护成本,也就是卡精度的实现方案。

对象精度:每个记录精确到一个对象,该对象里有字段含有跨代指针。 
卡精度:每个记录精确到一块内存区域,该区域内有对象含有跨代指针。

卡精度”所指的是用一种称为“卡表”(Card Table)的方式去实现记忆集,这也是目前最常用的一种记忆集实现形式,一些资料中甚至直接把它和记忆集混为一谈。前面定义中提到记忆集其实是一种“抽象”的数据结构,抽象的意思是只定义了记忆集的行为意图,并没有定义其行为的具体实现。卡表就是记忆集的一种具体实现,它定义了记忆集的记录精度、与堆内存的映射关系等。 (可以对比map 和HashMap)

卡表最简单的形式可以只是一个字节数组,而HotSpot虚拟机确实也是这样做的。以下这行代码是HotSpot默认的卡表标记逻辑:

CARD_TABLE [this address >> 9] = 0;

字节数组CARD_TABLE的每一个元素都对应着其标识的内存区域中一块特定大小的内存块,这个内存块被称作“卡页”(Card Page)。一般来说,卡页大小都是以2的N次幂的字节数,通过上面代码可以看出HotSpot中使用的卡页是2的9次幂,即512字节

img

一个卡页的内存中通常包含不止一个对象,只要卡页内有一个(或更多)对象的字段存在着跨代指针,那就将对应卡表的数组元素的值标识为1,称为这个元素变脏(Dirty),没有则标识为0。在垃圾收集发生时,只要筛选出卡表中变脏的元素,就能轻易得出哪些卡页内存块中包含跨代指针,把它们加入GC Roots中一并扫描。

http://www.lryc.cn/news/228817.html

相关文章:

  • Python实现WOA智能鲸鱼优化算法优化卷积神经网络分类模型(CNN分类算法)项目实战
  • 使用 Qt 实现监听网页是否响应,导出 Excel 表
  • Java通过JNI技术调用C++动态链接库的helloword测试
  • Python爬虫所需的常用库
  • Android Studio真机运行时提示“安装失败”
  • 【C语言数据结构————————二叉树】
  • 分组取每组数据的最大值和最小值的方法思路,为类似场景的数据分析提取提供思路,例如提取宗地内建筑的最高层数等可参考此方法思路
  • MyBatis 反射工具箱:带你领略不一样的反射设计思路
  • Netty第三部
  • 【C++入门篇】保姆级教程篇【下】
  • CCLink转Modbus TCP网关_CCLINK参数配置
  • 一文2000字从0到1使用压测神器JMeter进行压力测试!
  • 极狐GitLab CI 助力 .Net 项目研发效率和质量双提升
  • [协程]生成器协程调度器的实现-未完
  • Git之分支与版本->课程目标及知识点的应用场景,分支的场景应用,标签的场景应用
  • PHP正则提取或替换img标记属性
  • Git 命令行使用指南
  • Spring 常见面试题
  • caffe搭建squeezenet网络的整套工程
  • 【OWT】梳理构建的webrtc和owt mfc工程
  • 02 powershell服务器远程执行命令
  • LeetCode257. Binary Tree Paths
  • Linux下MSSQL (SQL Server)数据库无法启动故障处理
  • 2311极语言高亮说明书
  • 金蝶云星空与金蝶云星空对接集成盘亏单查询打通盘亏单新增
  • 深入理解 Django 信号机制
  • uniapp开发app应用从创建到上架
  • 为什么使用Golang而非Rust开发桌面应用?
  • 问题复盘|MySQL 数据记录中明明有值,使用 concat() 后得到的却一直是 null
  • 正点原子嵌入式linux驱动开发——Linux IIO驱动