JVM——HotSpot的算法细节实现

一、根节点枚举

        固定可作为GC Roots的节点主要在全局性的引用(如常量或类静态属性)与执行上下文(如栈帧中的本地变量表)中,尽管目标明确,但查找要做到高效很难。现在java应用越来越庞大,光方法区的大小就常有数百上千兆,里面的类、常量等更是恒河沙数,逐个检查以这里为起源的引用肯定得消耗不少时间。

        同时迄今为止,所有收集器在根节点枚举这一步时都是必须暂停用户线程的。根节点枚举必须在一个保障一致性的快照中进行。一致性的意思是整个枚举期间执行子系统看起来就像被冻结在某一个时间点上,不会出现分析过程中,根节点集合的对象引用关系还在不断的变化的情况,若这点不能满足,分析结果准确性也就无法保证

        由于目前主流java虚拟机使用的都是准确式垃圾收集(准确式内存管理:虚拟机可以知道内存中某个位置的数据具体是什么类型),所以当用户线程停顿时,不需要一个不漏的检查完所有执行上下文和全局的引用位置,虚拟机应当是有办法直接得到哪些地方存放着对象的引用。在HotSpot的解决方案里,是使用一组称为OopMap的数据结构来达到这个目的

        类加载完成时,HotSpot就会把对象内什么偏移量上是什么类型的数据计算出来,在即时编译过程中,也会在特定的位置记录下栈里和寄存器里哪些位置时引用。这样收集器扫描时就可以直接知道这些信息,并不需要真正一个不漏从方法区等GC Roots考试查找。

        下面的代码是 HotSpot 虚拟机客户端模式下生成的一段 String::hashCode() 方法的本地代码,可以看到在0x026eb7a9 处的 call 指令有 OopMap 记录,它指明了 EBX 寄存器和栈中偏移量为 16 的内存区域 中各有一个普通对象指针(Ordinary Object Pointer OOP )的引用,有效范围为从 call 指令开始直到0x026eb730(指令流的起始位置) +142 OopMap 记录的偏移量) =0x026eb7be ,即 hlt 指令为止。

二、安全点

        HotSpot没有为每一条指令都生成OopMap,上面提到的“类加载完成时,HotSpot就会把对象内什么偏移量上是什么类型的数据计算出来,在即时编译过程中,也会在特定的位置记录下栈里和寄存器里哪些位置时引用中提到的特定的位置记录了这些信息,这些位置被称为安全点(Safepoint)。因此,用户程序执行时并非在任意位置都能停下来进行垃圾收集,强制要求必须执行到安全点后才能暂停。所以,安全点的选定既不能太少以至于让收集器等待时间过长,也不能太频繁以至于过分增大运行时的内存负荷

        安全点位置的选取标准:是否具有让程序长时间执行的特征。因为每条指令执行的时间都非常短暂,程序不太可能因为指令流长度太长这样的原因而长时间执行,长时间执行的最明显特征就是指令序列的复用,例如方法调用、循环跳转、异常跳转等都属于指令序列复用,所以只有具有这些功能的指令才会产生安全点。

如何让所有线程都跑到最近的安全点停顿下来:

抢先式中断

        抢先式中断不需要线程的执行代码主动去配合。
        在垃圾收集发生时,系统首先把所有用户线程全部中断,如果发现有用户线程中断的地方不在安全点上,就恢复这条线程执行,让它一会儿再重新中断,直到跑到安全点上。
        现在几乎没有虚拟机实现采用抢先式中断来暂停线程响应GC事件。

主动式中断

        设置一个标志位,线程执行时会不停的主动轮询这个标志,一旦发现中断标志位为真自己在最近的安全点上主动中断挂起

        轮询标志的地方和安全点是重合的,另外还要加上所有创建对象和其他需要在Java堆上分配内存的地方,这是为了检查是否即将要发生垃圾收集,避免没有足够内存分配新对象。
        由于轮询操作在代码中会频繁出现,这要求它必须足够高效。HotSpot 使用内存保护陷阱的方式,把轮询操作精简至只有一条汇编指令的程度。
        下面代码清单中的 test 指令就是 HotSpot 生成的轮询指令,当需要暂停用户线程时,虚拟机把0x160100 的内存页设置为不可读,那线程执行到 test 指令时就会产生一个自陷异常信号,然后在预先注册的异常处理器中挂起线程实现等待,这样仅通过一条汇编指令便完成安全点轮询和触发线程中断了。

 

三、安全区域

        程序“不执行”时(没有分配处理器时间,如sleep和blocked状态),线程无法响应虚拟机的中断请求,不能走到安全点主动挂起,虚拟机也不可能等线程重新被激活。所以引入安全区域。

        安全区域是指能够确保在某一段代码片段之中,引用关系不会发生改变。因此,在这个区域中任意地方开始垃圾收集都是安全的。可以看作扩展延伸了的安全点。

        进入安全区域的代码,会标识自己已经进入安全区域,虚拟机发起垃圾收集时不用去管这些线程。当线程要离开安全区域时,他要检查虚拟机是否已经完成了根节点枚举(或垃圾收集过程中其他需要暂停用户线程的阶段),完成,那线程就当作没事发生,继续执行。否则一直等待直到收到可以离开安全区域的信号。

四、记忆集与卡表

        在分代收集中,为了解决对象跨代引用所带来的问题,在新生代中建立记忆集的数据结构,用以避免把整个老年代加进GC Roots扫描范围。事实上所有部分区域收集行为的垃圾收集器都会有跨代引用问题。

        记忆集是一种记录从非收集区域指向收集区域指针集合的抽象数据结构。不考虑效率和成本,最简单的实现用非收集区域中所有含跨代引用的对象数组来实现这个数据结构。

        在垃圾收集场景中,收集器只需要通过记忆集判断出某一块非收集区域是否存在指向收集区域的指针就行,不需要了解跨代指针的全部细节

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

  • 字节精度:每个记录精确到机器字长(就是处理器的寻址位数,如常见的32位或64位,这个精度决定了机器访问物理内存地址的指针长度),该字包含跨代指针。
  • 对象精度:每个记录精确到一个对象,该对象里有字段含有跨代指针。
  • 卡精度:每个记录精确到一块内存区域,该区域内有对象含有跨代指针。(用一种称为“卡表”的方式去实现记忆集,是常用记忆集实现方式之一。
        记忆集其实是一种“抽象 的数据结构,抽象的意思是只定义了记忆集的行为意图,并没有定义其行为的具体实现。卡表就是记忆集的一种具体实现,它定义了记忆集的记录精度、与堆内存的映射关系等。

卡表最简单的形式可以只是一个字节数组,下面这行代码是HotSpot默认的卡表标记逻辑:

CARD_TABLE[this address >> 9] = 0;
        字节数组CARD_TABLE 的每一个元素都对应着其标识的内存区域中一块特定大小的内存块,这个内存块被称作“ 卡页 Card Page )。一般来说,卡页大小都是以 2 N 次幂的字节数,通过上面代码可以看出HotSpot 中使用的卡页是 2 9 次幂,即 512 字节(地址右移 9 位,相当于用地址除以 512 )。那如果卡表标识内存区域的起始地址是0x0000 的话,数组 CARD_TABLE 的第 0 1 2 号元素,分别对应了地址范围为0x0000 0x01FF 0x0200 0x03FF 0x0400 0x05FF 的卡页内存块,如下图所示。
        一个卡页的内存中通常包含不止一个对象,只要卡页内有一个(或更多)对象的字段存在着跨代指针,那就将对应卡表的数组元素的值标识为1,称为这个元素变脏( Dirty ),没有则标识为 0 。在垃圾收集发生时,只要筛选出卡表中变脏的元素,就能轻易得出哪些卡页内存块中包含跨代指针,把它们加入GC Roots 中一并扫描。

五、写屏障

        何时变脏有其他分代区域中对象引用了本区域对象时,其对应的卡表元素就应该变脏,变脏时间点原则上应该发生在引用类型字段赋值的那一刻。

        如何变脏,即如何更新维护卡表

  • 若是解释执行的字节码,虚拟机负责每条字节码指令的执行,有充分的介入空间。
  • 若是编译执行,经过即时编译后的代码已经是纯粹的机器指令流了,这就必须找到一个在机器码层面的手段,把维护卡表的动作放到每一个赋值操作之中。

        HotSpot通过写屏障(Writer Barrier)技术维护卡表。写屏障可以看作在虚拟机层面对“引用类型字段赋值”这个动作的AOP切面,在引用对象赋值时会产生一个环形(Around)通知,供程序执行额外的动作,也就是说赋值前后都在写屏障的覆盖范围内。在赋值前的部分的写屏障叫做写前屏障(Pre-Write Barrier),赋值后则叫写后屏障(Post-Write Barrier)。

G1之前只用到写后屏障。

写后屏障更新卡表,如下图。

        除了写屏障开销外(相较于扫描整个老年代的代价低),卡表在高并发下面临着“伪共享”问题。中央处理器的缓存系统是以缓存行为单位存储,多线程修改独立变量,这些变量恰好共享同一个缓存行,就会彼此影响(写回,无效化或同步)而导致性能降低。

        解决伪共享办法:不采用无条件的写屏障,先检查卡表标记,只有卡表元素未被标记过时才将其标记变脏,即卡表更新逻辑变为:

if(CARD_TABLE[this address >> 9] != 0)CARD_TABLE[this address >> 9] = 0;
        在JDK 7 之后, HotSpot 虚拟机增加了一个新的参数 -XX +UseCondCardMark ,用来决定是否开启卡表更新的条件判断。开启会增加一次额外判断的开销,但能够避免伪共享问题,两者各有性能损耗,是否打开要根据应用实际运行情况来进行测试权衡。

六、并发的可达性分析

        在根节点枚举这个步骤中,GC ROOTS相比起整个java堆中全部的对象已经减少了很多,且在各种优化技巧(如OopMap)的加持下,它带来的停顿已经非常短暂且相对固定。可从GC Roots继续往下遍历对象图,这一步骤的停顿时间必定与java堆容量成正比关系:堆越大,存储的对象越多,对象图结构越复杂,要标记更多对象而产生的停顿时间的更长

        要知道包含“ 标记 阶段是所有追踪式垃圾收集算法的共同特征,如果这个阶段会随着堆变大而等比例增加停顿时间,其影响就会波及几乎所有的垃圾收集器,同理可知,如果能够削减这部分停顿时间的话,那收益也将会是系统性的。
首先了解一下为什么在一个能保障一致性的快照下才能进行对象图的遍历?我们使用三色标记辅助推导:
  • 白色:对象尚未被垃圾收集器访问到。在可达性分析刚刚开始阶段,所有阶段对象都是白色,分析结束阶段,仍为白色,即代表不可达。
  • 黑色:对象已经被垃圾收集器访问过,且这个对象的所有引用都已经扫描过。黑色对象代表已经扫描过,它是安全存活的,如有其他对象引用指向黑色对象,无须重新扫描。黑色对象不可能直接(不经过灰色对象)指向某个白色对象。
  • 灰色:对象已经被垃圾收集器访问过,但这个对象上至少存在一个引用还没有被扫描过。

        可达性分析的扫描过程,可以看作对象图上一股以灰色为波峰的波纹从黑向白推进的过程。用户线程冻结不会有任何问题。但用户线程并发,收集器在标记时,用户线程在修改引用,会导致两种结果:一种是把原本消亡的对象错误标记为存活,即产生浮动垃圾,下次收集即可,可以容忍。另一种是把原本存活的对象标记为已消亡,这就很致命了,程序肯定会因此发生错误,下面演示这样的致命错误是怎样产生的。

“对象消失”问题:原本应该是黑色的对象被误标为白色。

“对象消失”问题产生的条件(需要同时满足):

  • 赋值器插入了一条或多条从黑色对象到白色对象的新引用
  • 赋值器删除了全部从灰色对象到该白色对象的直接或间接引用

解决“对象消失”问题:增量更新原始快照

  • 增量更新:破坏第一个条件,当黑色对象插入新的指向白色对象的引用关系时,就将这个新插入的引用记录下来,等并发扫描结束后,再将这些记录过的引用关系中的黑色对象为根,重新扫描一次。简化理解为:黑色对象一旦新插入了指向白色对象的引用之后,他就变回灰色对象了。
  • 原始快照:破坏第二个条件,当灰色对象要删除指向白色对象的引用关系时,就将这个要删除的引用记录下来,在并发扫描结束之后,再将这些记录过的引用关系中的灰色对象为根,重新扫描一次。简化理解为:无论引用关系删除与否,都会按照刚刚开始扫描那一刻的对象图快照来进行搜索。
以上无论是对引用关系记录的插入还是删除,虚拟机的记录操作都是通过写屏障实现的。
        在 HotSpot虚拟机中,增量更新和原始快照这两种解决方案都有实际应用,譬如,CMS 是基于增量更新来做并发标记的,G1 Shenandoah 则是用原始快照来实现。

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

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

相关文章

Code interpreter生成无聊的APP:病理图像切割和提取

一、写在前面 机器学习100步不够分配了,所以开个新专栏,就叫做《Code interpreter生成无聊的APP》,旨在探索GPT-4官方插件Code interpreter的使用心路历程。 主要灵感来源:听户主说,她们在做病理组学图像标注和分割的…

如何用Apipost实现sign签名?

我们平常对外的接口都会用到sign签名,对不同的用户提供不同的apikey ,这样可以提高接口请求的安全性,避免被人抓包后乱请求。 如何用Apipost实现sign签名? 可以在Apipost中通过预执行脚本调用内置的JS库去实现预执行脚本是在发送请求之前自…

Wordcloud | 风中有朵雨做的‘词云‘哦!~

1写在前面 今天可算把key搞好了,不得不说🏥里手握生杀大权的人,都在自己的能力范围内尽可能的难为你。😂 我等小大夫也是很无奈,毕竟奔波霸、霸波奔是要去抓唐僧的。 🤐 好吧,今天是词云&#x…

【C++精华铺】8.C++模板初阶

目录 1. 泛型编程 2. 函数模板 2.1 函数模板的概念及格式 2.2 函数模板的原理 2.3 模板的实例化 2.4 模板参数的匹配原则 3. 类模板 3.1 类模板格式 3.2 类模板的实例化 1. 泛型编程 什么是泛型编程?泛型编程是避免使用某种具体类型而去使用某种通用类型来进行…

带你了解—使用内网穿透,公网远程访问本地硬盘文件

文章目录 前言1. 下载cpolar和Everything软件3. 设定http服务器端口4. 进入cpolar的设置5. 生成公网连到本地内网穿透数据隧道 总结 前言 随着云概念的流行,不少企业采用云存储技术来保存办公文件,同时,很多个人用户也感受到云存储带来的便利…

学习ts(四)联合类型、交叉类型、类型断言

联合类型 使用联合类型定义属性和方法,只要符合其中一种即可 let myPhone: string | number 010-7788 // let myPhone1: string | number true 因为没有包含boolean值 会报错const fn (something: number | boolean): boolean > {return !!something }con…

【CSS动画01--登录】

CSS动画01--登录 介绍代码HTMLCSSJS 介绍 当鼠标不同方向的划过时展示不同效果的登录&#xff0c;以上是一个简单的图片展示 代码 HTML <!DOCTYPE html> <html> <head><meta http-equiv"content-type" content"text/html; charsetutf-8&…

生物笔记——暑期学习笔记(四)

生物笔记——暑期学习笔记&#xff08;四&#xff09; 文章目录 前言一、R篇1. unname()2. duplicated()3. 数据提取4. 分组 二、生信篇1. 文本处理常用命令2. 命令输出1. 重定向2. 多命令执行 3. 文本工具4. 本地hmm鉴定1. hmmer软件安装2. 文件准备3. 基于hmm的鉴定 总结 前言…

【制作npm包5】npm包制作完整教程,我的第一个npm包

制作npm包目录 本文是系列文章&#xff0c; 作者一个橙子pro&#xff0c;本系列文章大纲如下。转载或者商业修改必须注明文章出处 一、申请npm账号、个人包和组织包区别 二、了解 package.json 相关配置 三、 了解 tsconfig.json 相关配置 四、 api-extractor 学习 五、npm包…

MySQL的配置文件my.cnf与my.ini

一、my.cnf与my.ini win系统&#xff0c;MySQL配置文件为my.ini 其他系统&#xff08;Ubuntu、CentOS、macOS)MySQL配置文件为my.cnf 二、my.cnf与my.ini的路径 2.1 默认路径 MySQL 的配置文件 my.cnf 可能位于多个位置&#xff0c;具体取决于安装方式和操作系统。以下是一…

2023年国赛数学建模思路 - 案例:最短时间生产计划安排

文章目录 0 赛题思路1 模型描述2 实例2.1 问题描述2.2 数学模型2.2.1 模型流程2.2.2 符号约定2.2.3 求解模型 2.3 相关代码2.4 模型求解结果 建模资料 0 赛题思路 &#xff08;赛题出来以后第一时间在CSDN分享&#xff09; https://blog.csdn.net/dc_sinor?typeblog 最短时…

Kotlin开发笔记:使用委托进行拓展

Kotlin开发笔记&#xff1a;使用委托进行拓展 导言 在OO语言(面向对象)中&#xff0c;我们经常会用到委托或者代理的思想。委托和代理在乍一看很相似&#xff0c;其实其各有各的侧重点&#xff0c;这里我引用ChatGpt的回答&#xff1a; 委托&#xff08;Delegation&#xff09…

在C中使用Socket实现多线程异步TCP消息发送

目录 基础知识开始实现主要函数说明结束语 在本篇文章中&#xff0c;我们会探讨如何在C语言中使用socket来实现多线程&#xff0c;异步发送TCP消息的系统。虽然C标准库并没有原生支持异步和多线程编程&#xff0c;但是我们可以结合使用POSIX线程&#xff08;pthread&#xff09…

Java解决四大查找(一)

Java解决四大查找 一.线性查找1.1 题目1.2 思路分析1.3 代码演示 二.二分查找(双指针法)2.1 题目2.2 思路分析(图解加文字)2.3 代码演示 一.线性查找 1.1 题目 在数组{1&#xff0c;8&#xff0c;1024&#xff0c;521&#xff0c;1889}中查找数字8&#xff0c;如果有&#xff…

地址解析协议-ARP

ARP协议 无论网络层使用何种协议&#xff0c;在实际网络的链路上传输数据帧时&#xff0c;最终必须使用硬件地址 地址解析协议&#xff08;Address Resolution Protocol&#xff0c;ARP&#xff09;&#xff1a;完成IP地址到MAC地址的映射&#xff0c;每个主机都有一个ARP高速缓…

【数据结构】二叉树篇| 纲领思路02+刷题

博主简介&#xff1a;努力学习的22级计算机科学与技术本科生一枚&#x1f338;博主主页&#xff1a; 是瑶瑶子啦每日一言&#x1f33c;: 所谓自由&#xff0c;不是随心所欲&#xff0c;而是自我主宰。——康德 目录 一、前言二、刷题1、翻转二叉树 2、二叉树的层序遍历✨3、 二…

线性代数再回顾

最近&#xff0c;在深度学习线性代数&#xff0c;之前大一的时候学过线性代数&#xff0c;但那纯属于是应试用的&#xff0c;考试一考完&#xff0c;啥都忘了&#xff0c;也说出不出个所以然&#xff0c;所以&#xff0c;在B站的MIT的线性代数以及3blue1brown线性代数的本质中去…

git命令使用

君子拙于不知己,而信于知己。——司马迁 清屏&#xff1a;clear 查看当前面板的路径&#xff1a;pwd 查看当前面板的文件&#xff1a;ls 创建文件夹&#xff1a;mkdir 文件夹名 创建文件&#xff1a;touch 文件名 删除文件夹&#xff1a;rm -rf 文件夹名 删除文件&#xff1a;r…

Remote Sensing,2023 | 基于SBL的分布式毫米波相干雷达成像的高效实现

Remote Sensing,2023 | 基于SBL的分布式毫米波相干雷达成像的高效实现 注1&#xff1a;本文系“无线感知论文速递”系列之一&#xff0c;致力于简洁清晰完整地介绍、解读无线感知领域最新的顶会/顶刊论文(包括但不限于 Nature/Science及其子刊; MobiCom, Sigcom, MobiSys, NSDI…

爬虫IP时效问题:优化爬虫IP使用效果实用技巧

目录 1. 使用稳定的代理IP服务提供商&#xff1a; 2. 定期检测代理IP的可用性&#xff1a; 3. 配置合理的代理IP切换策略&#xff1a; 4. 使用代理IP池&#xff1a; 5. 考虑代理IP的地理位置和速度&#xff1a; 6. 设置合理的请求间隔和并发量&#xff1a; 总结 在爬虫过…