《深入理解Java虚拟机》第三章读书笔记(二)——HotSpot垃圾回收算法实现(OopMap,安全点安全区域,卡表,写屏障,三色标记算法)
前面《深入理解Java虚拟机》第三章读书笔记(一)——垃圾回收算法我们学习了垃圾回收算法理论知识,下面我们关注下HotSpot垃圾回收算法的实现,分为以下几部分
- 对象是垃圾的判断依据 GC Roots 是如何高效扫描的
- 如何解决跨代引用对象的垃圾回收问题
- 如何降低垃圾回收STW的时长——并发可达性分析
1.GC Roots 是如何高效扫描的
固定作为GC Roots的节点主要分布在全局性的引用(常量,静态属性)于栈帧本地变量表等,如何快速从方法区中获取这些节点呢?
HotSpot使用一组称为OopMap的数据结构来实现快速的扫描哪些地方存在对象引用——一旦类加载动作完成的实还,HotSpot就会在对象内什么偏移量上是什么类型的数据计算出来,对于即时编译,也会在特定的位置记录下栈里和寄存器里哪些位置是引用。
根节点枚举的过程需要暂停用户线程(Stop the world 简称STW),这样可以扫描的过程在一个一致性快照中进行(用户线程都停止了不会该变对象的引用关系)
2.用户线程何时停止进行垃圾回收
2.1安全点
Oop Map 让HotSpot可以快速进行根节点枚举,但是用户线程可能正在运行改变引用关系的指令,如果为每一条指令都生成对应的Oop Map,那么将需要大量的空间。因此需要一个“特定的位置”,在这个位置引用关系不会再改变,可以维护Oop Map 并进行GC,这个位置称为——“安全点”,它决定了用户程序执行时并非在代码指令流的任意位置都能够停顿下来开始垃圾收集,而是强制要求必须执行到达安全点后才能够暂停。
安全点的选定不能太多,以至于增大运行时的负荷(太多意味着Oop Map的维护过于频繁),也不能太少导致垃圾收集器等待时间太长。安全点位置需要能让程序长时间执行(大部分指令的执行时间都很短),但是方法调用,循环跳转,异常跳转这种指令序列复用符合这个要求,具备这些功能的指令回产生安全点。如何在垃圾回收是,让用户线程跑到最近的安全点,然后停顿下来
主动式中断:当垃圾收集需要中断线程的时候,不直接对线程操作,仅仅简单地设置一 个标志位,各个线程执行过程时会不停地主动去轮询这个标志,一旦发现中断标志为真时就自己在最近的安全点上主动中断挂起。轮询标志的地方和安全点是重合的,另外还要加上所有创建对象和其他 需要在Java堆上分配内存的地方,这是为了检查是否即将要发生垃圾收集,避免没有足够内存分配新 对象。
由于轮询操作在代码中会频繁出现,这要求它必须足够高效。HotSpot使用内存保护陷阱的方式, 把轮询操作精简至只有一条汇编指令的程度。线程执行到这个汇编指令的会产生一个自陷异常信号,然后在预先注册的异常处理器中挂起线程实现等待,这样仅通过一条汇编指令便完成安全点轮询和触发线程中断了。
抢先式中断
抢先式中断不需要线程的执行代码 主动去配合,在垃圾收集发生时,系统首先把所有用户线程全部中断,如果发现有用户线程中断的地方不在安全点上,就恢复这条线程执行,让它一会再重新中断,直到跑到安全点上。现在几乎没有虚拟机实现采用抢先式中断来暂停线程响应GC事件。
2.2安全区域
安全点保证了用户线程在运行的时候,如何停止用户线程,让jvm进入垃圾回收状态。但是如果用户线程被阻塞而停止的时候呢?
如果一个线程处于 Sleep 或中断状态,它就不能响应 JVM 的中断请求,再运行到安全点(Safe Point) 上。因此 JVM 引入了 安全区域(Safe Region)。Safe Region 是指在一段代码片段中,引用关系不会发生变化。在这个区域内的任意地方开始 GC 都是安全的。线程在进入 Safe Region 的时候先标记自己已进入了 Safe Region,等到被唤醒时准备离开 Safe Region 时,先检查能否离开,如果 GC 完成了,那么线程可以离开,否则它必须等待直到收到安全离开的信号为止。
3.如何解决跨代引用对象的回收
1.记忆集与卡表
如果老年代引用了新生代的对象,回收新生代的时候,难道需要扫描全部老年代找出存在跨代引用的对象么?
垃圾收集器在新生代中建立了名为记忆集的对象,可以避免将整个老年代加入到GC Roots的扫描范围。记忆集是用于记录非收集区域指向收集区域的指针集合的抽象数据结构。
为了减少记忆集的空间成本,收集器只需要记忆集判断出某一块非收集区域是否存在向收集区域的指针就可以了,并不记录所有跨代指针细节,因此记忆集的具体实现——“卡表”只精确到一块内存区域(该区域内存在对象的跨代指针)。

2.写屏障维护卡表
卡表用于记录跨代指针,但是卡表中的元素何时进行维护,也就说出现跨代指针的时候如何记录在卡表中,跨代指针消除的时候如何清除卡表的内容?
hotSpot虚拟机使用写屏障进行维护,这个写屏障可以看作是赋值操作的AOP环形通知。有了写屏障之后,虚拟机会为赋值操作生成相应指令,进行维护卡表。
为了避免并发场景下,多线程操作卡表导致伪共享,虚拟机会先检查卡表是否未被标记,未被标记才会进行标记操作。
4.并发可达性分析——三色标记算法
可达性分析算法理论上必须在一个一致性快照中进行,一致性意味着需要冻结用户线程。在枚举GC Roots这个环节jvm使用OopMap让STW停顿时间减少,但是获得GC Roots之后继续遍历对象图的过程必然会随着堆越大而愈加耗时,导致停顿的时间更长。
那么如何减少这个停顿时间呢?——让可达性分析算法中的标记步骤可以和用户线程尽量并行,三色标记算法应运而生。
三色标记算法
三色是:黑色,白色,灰色。
把遍历对象图过程中遇到的对象,按照是否访问过这个条件标记成以下三种颜色:
- 白色:表示对象尚未被垃圾收集器访问过。显然在可达性分析刚刚开始的阶段,所有的对象都是白色的,若在分析结束的阶段,仍然是白色的对象,即代表不可达。
- 黑色:表示对象已经被垃圾收集器访问过,且这个对象的所有引用都已经扫描过。黑色的对象代表已经扫描过,它是安全存活的,如果有其他对象引用指向了黑色对象,无须重新扫描一遍。黑色对象不可能直接(不经过灰色对象)指向某个白色对象。
- 灰色:表示对象已经被垃圾收集器访问过,但这个对象上至少存在一个引用还没有被扫描过。

上图描述了三色标记的流程。但是如果标记的时候用户线程在修改引用关系,导致对象图关系改变,可能导致出现错误。
错标:是垃圾的对象,没有被标记为垃圾

这种情况本应灰色的垃圾E,以及和它关联的对象 F,G都不会会被回收(E被视为和GC Roots关联导致错误的任务,G,F也不垃圾),这种称为浮动垃圾(不和GC Roots关联如同漂浮无依无靠的垃圾)浮动垃圾的问题影响不是很大,可能就是暂时的浪费一点内存,它肯定抗不过下一轮GC
错杀

这种情况十分严重,但是存在补救方法:
增量更新
当黑色对象插入指向白色对象的引用关系时,将这个插入的引用记录下来,并发扫描结束后再将这些及引用关系的黑色对象为根重新扫描一次。
例如D插入了对G的引用当并发扫描结束后,以D为根再次进行扫描,这时候G就会被标记为黑色,从而不被回收。
原始快照
当灰色对象要删除指向白色对象的引用关系时,就将删除的引用记录下来,并发扫描结束后,再将这些记录过引用关系的灰色对象为根,重新扫描一次。
例如E删除了对G的引用,但是记录下了E->G,并发扫描结束后,再扫描E并且结合E->G将G标黑,从而让G不被回收。
《深入理解Java虚拟机》第三章读书笔记(二)——HotSpot垃圾回收算法实现(OopMap,安全点安全区域,卡表,写屏障,三色标记算法)的更多相关文章
- 《深入理解java虚拟机-高效并发》读书笔记
Java内存模型与线程 概述 多任务处理在现代计算机操作系统中几乎已是一项必备的功能,多任务运行是压榨手段,就如windows一样,我们使劲的压榨它运行多个任务,俱要high又要耍.并发则是另外一种更 ...
- 《深入了解java虚拟机》高效并发读书笔记——Java内存模型,线程,线程安全 与锁优化
<深入了解java虚拟机>高效并发读书笔记--Java内存模型,线程,线程安全 与锁优化 本文主要参考<深入了解java虚拟机>高效并发章节 关于锁升级,偏向锁,轻量级锁参考& ...
- 深入理解java虚拟机-第13章-线程安全与锁优化
第十三章 线程安全与锁优化 线程安全 java语言中的线程安全 1 不可变.Immutable 的对象一定是线程安全的 2 绝对线程安全 一个类要达到不管运行时环境如何,调用者都不需要额外的同步措施, ...
- java虚拟机学习-JVM调优总结-分代垃圾回收详述(9)
为什么要分代 分代的垃圾回收策略,是基于这样一个事实:不同的对象的生命周期是不一样的.因此,不同生命周期的对象可以采取不同的收集方式,以便提高回收效率. 在Java程序运行的过程中,会产生大量的对象, ...
- 深入理解java虚拟机_第二章_读书笔记
1.本章内容目录: 概述 运行时数据区域 程序计数器 java虚拟机栈 本地方法栈 java堆 方法区 运行时常量池 直接内存 HotSpot虚拟机对象探秘 对象的创建 对象的内存布局 对象的访问定位 ...
- 深入理解Java虚拟机-第1章-走进Java-读书笔记
第 1 章 走近 Java 前言 Java 的技术体系主要是由支撑 Java 程序运行的虚拟机.为各开发领域提供接口支持的 Java API.Java 编程语言及许许多多的第三方 Java 框架(如 ...
- 《深入理解java虚拟机》第二章 Java内存区域与内存溢出异常
第二章 Java内存区域与内存溢出异常 2.2 运行时数据区域
- 深入理解java虚拟机-第六章
第6章 类文件 6.3 Class类文件的结构 Class文件是一组以8位字节为基础单位的二进制流. Class文件格式采用一种类似C语言结构伪结构存储数据,这种伪结构中只有两种数据类型:无符号数和表 ...
- 《深入理解java虚拟机》第3版笔记3
第3章 垃圾收集器与内存分配策略 可达性分析算法 在Java技术体系里面,固定可作为GC Roots的对象包括以下几种: 在虚拟机栈(栈帧中的本地变量表)中引用的对象,譬如各个线程被调用的方法堆栈中使 ...
- Linux内核分析第三章读书笔记
第三章 进程管理 3.1 进程 进程就是处于执行期的程序 进程就是正在执行的程序代码的实时结果 线程:在进程中活动的对象.每个线程都拥有一个独立的程序计数器.进程栈和一组进程寄存器. 内核调度的对象是 ...
随机推荐
- LoadRunner11脚本小技能之添加请求头+定义变量+响应内容乱码转换打印+事务拆分
一.添加请求头 存在一些接口,发送请求时需要进行权限验证.登录验证(不加请求头时运行脚本,接口可能会报401等等),所以需要在脚本中给对应请求添加请求头.注意:请求头需在请求前添加,包含url类.su ...
- WPF之BackgroundWorker
BackgroundWorker类允许您在单独的线程上执行某个可能导致用户界面(UI)停止响应的耗时操作,下面来介绍一下这个线程类BackgroundWorker,大家可以结合这位大佬的这篇文章,说的 ...
- Go语言核心36讲43-----io包中接口的好处与优势
上一篇文章中,我主要讲到了io.Reader的扩展接口和实现类型.当然,io代码包中的核心接口不止io.Reader一个. 我们基于它引出的一条主线,只是io包类型体系中的一部分.我们很有必要再从另一 ...
- Huawei OJ 题解 - 31. 整数拆分 - Go 参考解答
## 简介- 详情:http://oj.rnd.huawei.com/problems/31/details- 难度:简单## 思路TODO## 用例TODO## 解答```gopackage mai ...
- vcenter异常死机无法重启
esxi主机异常掉电重启后,vcenter启动失败 查阅相关资料发现,一般是由于时间同步异常造成, 推荐方法是先确认bios硬件时间已同步,再删除旧的本地服务json文件,重启vcenter的服务. ...
- Java开发学习(四十二)----MyBatisPlus查询语句之条件查询
一.条件查询的类 MyBatisPlus将书写复杂的SQL查询条件进行了封装,使用编程的形式完成查询条件的组合. 这个我们在前面都有见过,比如查询所有和分页查询的时候,都有看到过一个Wrapper类, ...
- cv2.imread opencv读取不到图片问题
解决办法 cv2.imread 路径中包含中文,改为英文 其他 这个问题解决了很久,都属于库的问题
- Vue2基本组件间通信
Vue2组件通信的基础方式 自己的理解:组件化通信,无非就是数据你传我,我传你,两个组件的相互交流,方法很多,下方有图示(此篇建议小白阅读,大神的话也不会看,哈哈哈哈!仅供参考,有不同的意见可以一起交 ...
- HTTP2 协议长文详解
一.HTTP2 简介 HTTP2 是一个超文本传输协议,它是 HTTP 协议的第二个版本.HTTP2 主要是基于 google 的 SPDY 协议,SPDY 的关键技术被 HTTP2 采纳了,因此 S ...
- intel Pin:动态二进制插桩的安装和使用,以及如何开发一个自己的Pintool
先贴几个你可能用得上的链接 intel Pin的官方介绍Pin: Pin 3.21 User Guide (intel.com) intel Pin的API文档Pin: API Reference ( ...
