《深入理解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 进程 进程就是处于执行期的程序 进程就是正在执行的程序代码的实时结果 线程:在进程中活动的对象.每个线程都拥有一个独立的程序计数器.进程栈和一组进程寄存器. 内核调度的对象是 ...
随机推荐
- Debian玩红警2
Debian玩红警2 1. 安装wine sudo apt update sudo apt install wine wine --version wine-5.0.3 (Debian 5.0.3-3 ...
- 图文详解丨iOS App上架全流程及审核避坑指南
App Store作为苹果官方的应用商店,审核严格周期长一直让用户头疼不已,很多app都"死"在了审核这一关,那我们就要放弃iOS用户了吗?当然不是!本期我们从iOS app上架流 ...
- CH58X服务修改
在对ble系列应用时,很多时候拿手机充当主机.在使用ble 调试助手时常会用到write.read.notify等功能.有时可能会根据自己的需求对这些服务进行修改.下图是官方例程体现出的service ...
- CF815D
模拟赛遇到的题目. 看各位大佬的做法都不是很懂,于是自己一通乱搞搞出来了. 题意 翻译清楚的不能再清楚了 做法 为了方便叙述,我们将每个给出的三元组表示成 \((a_i,b_i,c_i)\),所选的三 ...
- 为什么 softmax 计算时要先减去最大值
根据 softmax 最基本的定义,计算公式如下所示: $$S_i=\frac{e^{x_i}}{\sum_j e^{x_j}}$$ 原理也很简单,将原向量变为分布的形式(和为1). 看似很美好,但是 ...
- 【云原生 · Kubernetes】部署 kube-proxy 组件
个人名片: 因为云计算成为了监控工程师 个人博客:念舒_C.ying CSDN主页️:念舒_C.ying kube-proxy 运行在所有 worker 节点上,它监听 apiserver 中 se ...
- AWS启示录:创新作帆,云计算的征途是汪洋大海
全文13100字,预计阅读时间15到20分钟. 开篇:创新是AWS发展的最持久驱动力 云计算,新世纪以来最伟大的技术进步之一,从2006年 Amazon Web Service(以下简称AWS)初创时 ...
- from 表单非空验证以及多表单提交
开发中我们常用到$('#formid').serialize()方法进行表单序列化提交,但也相应催生了表单的非空严重以及多表单提交. form html: <form id="form ...
- CSS伪类使用详解
基本描述 CSS伪类是很常用的功能,主要应用于选择器的关键字,用来改变被选择元素的特殊状态下的样式. 伪类类似于普通CSS类的用法,是对CSS选择器的一种扩展,增强选择器的功能. 目前可用的伪类有大概 ...
- 【面试真题】ThoughtWorks-编程结对技术面试(一面)-2022年2月11日
一.技术问题 1.Hbase (1)介绍 (2)项目中是否有用到 于:存大量数据(千万),考虑性能,方便进行数据处理,对其进行分析 自己:ADS层和Flink的数据,DWD计算出的的中间层数据存入DW ...
