JVM是如何解决跨代引用问题的?
本文已收录至Github,推荐阅读 Java随想录
微信公众号:Java随想录
CSDN: 码农BookSea
不知道自己的无知,乃是双倍的无知。——柏拉图
跨代引用问题
跨代引用是指新生代中存在对老年代对象的引用,或者老年代中存在对新生代的引用。
假如要现在进行一次只局限于新生代区域内的收集(Minor GC),但新生代中的对象是完全有可能被老年代所引用的,为了找出该区域中的存活对象,不得不在固定的 GC Roots 之外,再额外遍历整个老年代中所有对象来确保可达性分析结果的正确性,反过来也是一样。无疑会为内存回收带来很大的性能负担。
别慌,JVM的设计者已经考虑了这个场景,并想到了解决办法,那就是使用一种叫做:记忆集(Remembered Set)的数据结构。
记忆集
记忆集位于新生代中。用以避免把整个老年代加进GC Roots扫描范围。
记忆集的作用和我们之前讲的OopMap很相似,维护了类似一种映射表的关系,避免了全局扫描,本质是用空间换时间。
记忆集是一种用于记录从非收集区域指向收集区域的指针集合的抽象数据结构。注意这里的说辞:抽象。意思就是说记忆集是一种逻辑上的概念,并没有规定具体的实现,类似方法区。下文我们会说到卡表,可以把记忆集和卡表的关系理解为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。在垃圾收集发生时,只要筛选出卡表中变脏的元素,就能轻易得出哪些卡页内存块中包含跨代指针,把它们加入GC Roots中一并扫描。
简单来说,就是卡页的字节数组只有0和1两种状态,1表示哪些内存区域存在跨代指针,那么只要把1的加入GC Roots中一并扫描,就能知道哪些进行跨代引用了,这样就不用挨个去扫描了。
OK,我们还剩下一个问题,这个问题OopMap也遇到过。卡表元素如何维护?何时变脏、谁来把它们变脏等。
HotSpot解决的办法是使用写屏障。
写屏障
先来解决何时变脏的问题,这个问题很简单,即其他分代区域中对象引用了本区域对象时,其对应的卡表元素就应该变脏,变脏时间点原则上应该发生在引用类型字段赋值的那一刻。
但问题是如何变脏,即如何在对象赋值的那一刻去更新维护卡表,在HotSpot虚拟机里是通过写屏障(Write Barrier)解决的。
注意:这里提到的 写屏障 和 volatile 的写屏障不是一回事。
写屏障可以看作在虚拟机层面对“引用类型字段赋值”这个动作的AOP切面,在引用对象赋值时会产生一个环形(Around)通知。用过Spring的弟兄们对AOP肯定不陌生。
在赋值前的部分的写屏障叫作写前屏障(Pre-Write Barrier),在赋值后的则叫作写后屏障(Post-Write Barrier)。HotSpot虚拟机的许多收集器中都有使用到写屏障,但直至G1收集器出现之前,其他收集器都只用到了写后屏障。
应用写屏障后,虚拟机就会为所有赋值操作生成相应的指令,一旦收集器在写屏障中增加了更新卡表操作,无论更新的是不是老年代对新生代对象的引用,每次只要对引用进行更新,就会产生额外的开销,不过这个开销与Minor GC时扫描整个老年代的代价相比还是低得多的。
写屏障的伪共享问题
卡表在高并发场景下还面临着“伪共享”(False Sharing)问题。伪共享是处理并发底层细节时一种经常需要考虑的问题,号称并发的隐形杀手,现代中央处理器的缓存系统中是以缓存行(Cache Line)为单位存储的,当多线程修改互相独立的变量时,如果这些变量恰好共享同一个缓存行,就会彼此影响(写回、无效化或者同步)而导致性能降低,这就是伪共享问题。
为了避免伪共享问题,一种简单的解决方案是不采用无条件的写屏障,而是先检查卡表标记,只有当该卡表元素未被标记过时才将其标记为变脏,即将卡表更新的逻辑变为以下代码所示:
相当于说就是多了一个if判断条件。
if (CARD_TABLE [this address >> 9] != 0)
CARD_TABLE [this address >> 9] = 0;
在JDK 7之后,HotSpot虚拟机增加了一个新的参数-XX:+UseCondCardMark(默认是关闭的),用来决定是否开启卡表更新的条件判断。开启会增加一次额外判断的开销,但能够避免伪共享问题,两者各有性能损耗,是否打开要根据应用实际运行情况来进行测试权衡。
如果本篇博客有任何错误和建议,欢迎给我留言指正。文章持续更新,可以关注公众号第一时间阅读。
JVM是如何解决跨代引用问题的?的更多相关文章
- Nginx反向代理和Node.js后端解决跨域问题
最近在写自己的博客,涉及到跨域的问题,自己捣鼓许久,终于解决了.然后总结一下,记录一下,日后遇到类似的问题的时候也可以得到一些启发. 一.什么是跨域 跨域,指的是浏览器不能执行其他网站的脚本.它是由浏 ...
- 【Java EE 学习 19】【使用过滤器实现全站压缩】【使用ThreadLocal模式解决跨DAO事务回滚问题】
一.使用过滤器实现全站压缩 1.目标:对网站的所有JSP页面进行页面压缩,减少用户流量的使用.但是对图片和视频不进行压缩,因为图片和视频的压缩率很小,而且处理所需要的服务器资源很大. 2.实现原理: ...
- ajax 、ajax的交互模型、如何解决跨域问题
1.ajax是什么? — AJAX全称为“AsynchronousJavaScript and XML”(异步JavaScript和XML),是一种创建交互式网页应用的网页开发技术. — 不是一种新技 ...
- js中ajax如何解决跨域请求
js中ajax如何解决跨域请求,在讲这个问题之前先解释几个名词 1.跨域请求 所有的浏览器都是同源策略,这个策略能保证页面脚本资源和cookie安全 ,浏览器隔离了来自不同源的请求,防上跨域不安全的操 ...
- 一步一步学习SignalR进行实时通信_3_通过CORS解决跨域
原文:一步一步学习SignalR进行实时通信_3_通过CORS解决跨域 一步一步学习SignalR进行实时通信\_3_通过CORS解决跨域 SignalR 一步一步学习SignalR进行实时通信_3_ ...
- c# WebApi之解决跨域问题:Cors
什么是跨域问题 出于安全考虑,浏览器会限制脚本中发起的跨站请求,浏览器要求JavaScript或Cookie只能访问同域下的内容.由于这个原因,我们不同站点之间的数据访问会被拒绝. Cors解决跨域问 ...
- [转] js前端解决跨域问题的8种方案(最新最全)
1.同源策略如下: URL 说明 是否允许通信 http://www.a.com/a.jshttp://www.a.com/b.js 同一域名下 允许 http://www.a.com/lab/a.j ...
- [转]html5: postMessage解决跨域和跨页面通信的问题
[转]html5: postMessage解决跨域和跨页面通信的问题 平时做web开发的时候关于消息传递,除了客户端与服务器传值,还有几个经常会遇到的问题: 多窗口之间消息传递(newWin = wi ...
- 后端调用接口在通过webService发布 解决跨域问题
1.新建一个空的项目 2.添加一个WebService新项 asmx格式的 3.在这里面写方法 加上[WebMethod]标识 前端就可以调用 4.发布WebService 右键服务 添加服 ...
- 外部调用mvc的api方法时,如何解决跨域请求问题?
首先,创建一个mvc项目(包含webapi),我们模拟一个场景 1)在项目的Controller 创建一个WeiXinApiController public class WeiXinApiContr ...
随机推荐
- 驱动开发:内核监控FileObject文件回调
本篇文章与上一篇文章<驱动开发:内核注册并监控对象回调>所使用的方式是一样的都是使用ObRegisterCallbacks注册回调事件,只不过上一篇博文中LyShark将回调结构体OB_O ...
- Windows下pip换成清华源
1.在C:\Users\用户名\ 下创建 pip 文件夹2.在文件夹内创建pip.ini 文件, 添加如下内容: [global] timeout = 6000 index-url = https:/ ...
- LAPM概述及配置
一.LAMP概述 1.1LAMP的概念 LAMP架构是目前成熟的企业网站应用模式之一,指的是协同工作的一整套系统和相关软件,能够提供动态web站点服务及其应用开发环境 LAMP是一个缩写词,具体包括L ...
- 图扑软件 3D 组态编辑器,低代码零代码构建数字孪生工厂
行业背景 随着中国制造 2025 计划的提出,新一轮的工业改革拉开序幕.大数据积累的指数级增长为智能商业爆发奠定了良好的基础,传统制造业高污染.高能耗.低效率的生产模式已不符合现代工业要求. 图扑拖拽 ...
- redis位图(bitmap)常用命令的解析
描述 bitmap是redis封装的用于针对位(bit)的操作,其特点是计算效率高,占用空间少,常被用来统计用户签到.登录等场景 常用命令及解析 常用命令 setbit key offset va ...
- mysql是如何实现mvcc的
mvcc的概念 mvcc即多版本并发控制,是一种并发控制的策略,能让数据库在高并发下做到安全高效的读写,提升数据库的并发性能; 是一种用来解决并发下读写冲突的无锁解决方案,为事务分配单向增长时间戳,为 ...
- 万万没想到,除了香农计划,Python3.11竟还有这么多性能提升!
众所周知,Python 3.11 版本带来了较大的性能提升,但是,它具体在哪些方面上得到了优化呢?除了著名的"香农计划"外,它还包含哪些与性能相关的优化呢?本文将带你一探究竟! 作 ...
- Java安全之Mojarra JSF反序列化
Java安全之Mojarra JSF反序列化 About JSF JavaServer Faces,新一代的Java Web应用技术标准,吸收了很多Java Servlet以及其他的Web应用框架的特 ...
- 云实例初始化工具cloud-init简介
项目简介 cloud-init是一款用于初始化云服务器的工具,它拥有丰富的模块,能够为云服务器提供的能力有:初始化密码.扩容根分区.设置主机名.注入公钥.执行自定义脚本等等,功能十分强大. 目前为止c ...
- 区分mbr与gpt分区
查看分区类型 [root@localhost ~]# parted -l|egrep 'dev/|Part' Warning: Unable to open /dev/sr0 read-write ( ...