| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> 数据结构与算法 -> HopSpot虚拟机垃圾回收算法实现细节 -> 正文阅读 |
|
[数据结构与算法]HopSpot虚拟机垃圾回收算法实现细节 |
HopSpot虚拟机垃圾回收算法实现细节Java虚拟机通过常见的对象存活判定算法:比如引用计数法、可达性分析算法来实现对垃圾对象的标记过程,通过常见的一些垃圾收集算法:比如标记-清除、标记-整理、复制等来实现对垃圾的回收过程。Java虚拟机在实现这些算法时,必须对算法的执行效率有严格的考量,才能保证虚拟机的高效运行。 1. 根节点枚举——如何标记垃圾对象在可达性分析算法中,从GC Roots集合开始进行自顶向下的搜索,每一条搜索路径都是一条引用链,当一个对象不在与任何一条引用链相关联时,可以被标记为垃圾对象。可达性分析算法的目标是找出所有与引用链不关联的对象。目前随着Java技术的不断发展,Java企业级应用越做越庞大,光是方法区的大小就常常有数百上千兆,里面包含大量的类、常量等信息,而方法区中类的静态属性引用的对象和常量引用的对象可以作为固定的GC Roots,如果以方法区为起点来检查每一个引用,势必会消耗大量的时间,造成查找过程效率低下。 解决方案:当用户线程停顿下之后,并不需要一个不漏的检查完所有执行上下文和全局的引用位置(虚拟机有办法可以直接得到哪些地方存放着对象引用)。HotSpot虚拟机使用一组成为OopMap的数据结构来实现(OopMap介绍参考:[1] 浅谈JVM中的OopMap ; [2] 虚拟机OopMap),HotSpot会在类加载完成时计算出对象内什么偏移量上是什么类型的数据,也会在特定位置记录下栈里和寄存器里哪些位置是引用。这样在扫描时就可以直接得知信息了。
补充说明:迄今为止所有的垃圾收集器在根节点枚举这一步都是需要暂停用户线程的(也就是在初始标记时会触发 Stop The World),根节点枚举需要在一个能保证一致性的快照中进行(即在枚举过程中根节点集合的对象应用关系不会发生变化)。现在的可达性分析算法耗时最长的查找引用链的过程已经可以做到和用户线程一起并发执行。 2. 安全点——如何停顿用户线程HotSpot可以在OopMap的协助下快速准确的完成GC Roots的枚举,但是导致OopMap内容变化的指令非常多,不能为每一条指令都生成对应的OopMap(会浪费大量的额外存储空间)。HotSpot会在特定位置记录这些信息,也就是 “安全点”,安全点决定了用户程序执行时并非在代码指令流的任意位置都能够停顿下来开始进行垃圾收集,而是强制要求必须执行到达安全点后才能暂停。 在垃圾收集时让所有线程都跑到最近的安全点的两种方案:抢断式中断 和 主动式中断:
补充说明:轮询操作在代码中会频繁出现,HotSpot使用内存保护陷进的方式把轮询操作精简至只有一条汇编指令的程度,比较高效。 3. 安全区域——如何停顿处于Sleep、Blocked状态的线程安全点机制可以保证程序在执行时,在不太长的时间内就会遇到可进入垃圾收集过程的安全点,那如果程序不执行时(没有分配处理器时间)呢?典型场景就是用户线程处于Sleep状态或者Blocked状态,这时候用户线程无法响应虚拟机的中断请求,不能再走到安全点去中断挂起自己。安全区域指能够确保在某一段代码片段之中,引用关系不会发生变化,在这个区域中的任意地方进行垃圾收集都是安全的。安全区域可以当做是扩展延长了的安全点。 补充说明:当用户线程进入安全区域时,这段时间内如果虚拟机要进行垃圾收集,则不用管这些已声明自己在安全区域中的线程;当线程要离开安全区域,它要检查虚拟机是否已经完成了根节点枚举过程,如果完成则当做无事发生,进行执行,否则它就必须一直等待,直到收到可以离开安全区域的信号为止。 4. 记忆表与卡表——如何处理跨代引用问题
垃圾回收器在新生代中会建立 “记忆表” 来避免将整个老年代加入到 GC Roots的扫描范围中。所有涉及到部分区域收集的垃圾回收器都会产生跨代引用问题。“记忆表” 是一种用于记录从非收集区域指向收集区域的指针集合的抽象数据结构,最简单的实现方式是:用非收集区域中所有含跨代引用的对象数组来实现。
在垃圾收集时,垃圾回收器只需要遍历 “记忆表” 来判断某一块非收集区域是否存在有指向收集区域的指针就可以了,并不需要了解这些跨代指针的全部细节。当然这样做的效率和成本都非常的高昂,在设计 “记忆表” 时可以考虑如下更加粗略的记录粒度来节省成本:
卡精度就是指的 “卡表” ,是目前最常用的一种记忆集具体实现形式,定义了记忆集的记录精度、与堆内存的映射关系等。(卡表和记忆集的关系如果HashMap和Map的关系)。HotSpot虚拟机以字节数组的形式实现卡表:
数组的每一个元素对应其标识的内存区域中一块特定大小的内存块,称为 “卡页” ,大小是512个字节。一个 “卡页” 通常包含不止一个对象,只要卡页中存在一个或以上对象的字段存在着跨代指针,就将对应的卡表的数组元素设置为1,称这个元素变 “脏” ,否则标识为0。在垃圾收集时,只筛选出卡表中变脏的数据,就知道哪些页内存块包含跨代指针。 5. 写屏障——如何维护卡表状态通过卡表来解决跨代引用问题,那么就存在另一个问题:卡表何时进行变脏?如何变脏?也就是卡表如何进行维护的问题。 毫无疑问,当有其他分代区域中对象引用了本区域对象时,其对应的卡表元素就会变脏(数组元素标识为1)。变脏的时间点原则上发生在引用类型字段赋值的那一刻。
应用写屏障之后的虚拟机会为所有的赋值操作生成相应的指令,一旦收集器在写屏障中增加了更新卡表的操作,无论更新的是不是老年代对新生代对象的引用,每次只要对引用进行更新,就会产生额外的开销,但这个开销与Minor GC时扫描的整个老年代的代价相比还是低的多的。 6. 并发的可达性分析——如何解决用户、垃圾收集线程并发执行下的标记问题
想要解决或者降低用户线程的停顿时间,可以让用户线程和垃圾收集线程并发执行,那么就需要解决在标记过程中产生的动态标记的问题。在进行可达性分析时,引入了三色标记作为辅助工具进行遍历:
接下来通过示例图来演示一下可达性分析的过程: step 1 :初始状态只有GC Roots是黑色的,对象只有被黑色对象引用才能存活,否则会死亡。
step 3 :扫描完成后,所有的白色对象都是垃圾对象,需要被清理,黑色对象全部安全存活。 step 4 :用户线程在标记过程中进行了并发修改了引用关系,比如在扫描灰色节点时,原本的引用被切断了(绿色虚线),同时原来引用的对象又与已经扫描过的黑节点建立了新引用(红色实线)。 step 5 :这种切断后重新被黑色对象引用的对象可能是原来的引用链中的一部分,由于黑色对象不会重复扫描,将导致扫描结束后出现的两个被黑色对象引用的对象仍然是白色,这个对象(蓝色实线指的对象)将被回收,非常危险。
破坏两个条件的任意一个即可防止发生 “对象消失” ,由此产生两种解决方案:
本文参考周志明老师的《深入理解Java虚拟机》 |
|
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 | -2024/11/26 8:18:57- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |