系列文章目录和关于我

前面《深入理解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之后继续遍历对象图的过程必然会随着堆越大而愈加耗时,导致停顿的时间更长。

那么如何减少这个停顿时间呢?——让可达性分析算法中的标记步骤可以和用户线程尽量并行,三色标记算法应运而生。

三色标记算法

三色是:黑色,白色,灰色。

把遍历对象图过程中遇到的对象,按照是否访问过这个条件标记成以下三种颜色:

  • 白色:表示对象尚未被垃圾收集器访问过。显然在可达性分析刚刚开始的阶段,所有的对象都是白色的,若在分析结束的阶段,仍然是白色的对象,即代表不可达。
  • 黑色:表示对象已经被垃圾收集器访问过,且这个对象的所有引用都已经扫描过。黑色的对象代表已经扫描过,它是安全存活的,如果有其他对象引用指向了黑色对象,无须重新扫描一遍。黑色对象不可能直接(不经过灰色对象)指向某个白色对象。
  • 灰色:表示对象已经被垃圾收集器访问过,但这个对象上至少存在一个引用还没有被扫描过。

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

  1. 错标:是垃圾的对象,没有被标记为垃圾

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

  2. 错杀

    这种情况十分严重,但是存在补救方法:

    • 增量更新

      当黑色对象插入指向白色对象的引用关系时,将这个插入的引用记录下来,并发扫描结束后再将这些及引用关系的黑色对象为根重新扫描一次。

      例如D插入了对G的引用当并发扫描结束后,以D为根再次进行扫描,这时候G就会被标记为黑色,从而不被回收。

    • 原始快照

      当灰色对象要删除指向白色对象的引用关系时,就将删除的引用记录下来,并发扫描结束后,再将这些记录过引用关系的灰色对象为根,重新扫描一次。

      例如E删除了对G的引用,但是记录下了E->G,并发扫描结束后,再扫描E并且结合E->G将G标黑,从而让G不被回收。

《深入理解Java虚拟机》第三章读书笔记(二)——HotSpot垃圾回收算法实现(OopMap,安全点安全区域,卡表,写屏障,三色标记算法)的更多相关文章

  1. 《深入理解java虚拟机-高效并发》读书笔记

    Java内存模型与线程 概述 多任务处理在现代计算机操作系统中几乎已是一项必备的功能,多任务运行是压榨手段,就如windows一样,我们使劲的压榨它运行多个任务,俱要high又要耍.并发则是另外一种更 ...

  2. 《深入了解java虚拟机》高效并发读书笔记——Java内存模型,线程,线程安全 与锁优化

    <深入了解java虚拟机>高效并发读书笔记--Java内存模型,线程,线程安全 与锁优化 本文主要参考<深入了解java虚拟机>高效并发章节 关于锁升级,偏向锁,轻量级锁参考& ...

  3. 深入理解java虚拟机-第13章-线程安全与锁优化

    第十三章 线程安全与锁优化 线程安全 java语言中的线程安全 1 不可变.Immutable 的对象一定是线程安全的 2 绝对线程安全 一个类要达到不管运行时环境如何,调用者都不需要额外的同步措施, ...

  4. java虚拟机学习-JVM调优总结-分代垃圾回收详述(9)

    为什么要分代 分代的垃圾回收策略,是基于这样一个事实:不同的对象的生命周期是不一样的.因此,不同生命周期的对象可以采取不同的收集方式,以便提高回收效率. 在Java程序运行的过程中,会产生大量的对象, ...

  5. 深入理解java虚拟机_第二章_读书笔记

    1.本章内容目录: 概述 运行时数据区域 程序计数器 java虚拟机栈 本地方法栈 java堆 方法区 运行时常量池 直接内存 HotSpot虚拟机对象探秘 对象的创建 对象的内存布局 对象的访问定位 ...

  6. 深入理解Java虚拟机-第1章-走进Java-读书笔记

    第 1 章 走近 Java 前言 Java 的技术体系主要是由支撑 Java 程序运行的虚拟机.为各开发领域提供接口支持的 Java API.Java 编程语言及许许多多的第三方 Java 框架(如 ...

  7. 《深入理解java虚拟机》第二章 Java内存区域与内存溢出异常

    第二章 Java内存区域与内存溢出异常 2.2 运行时数据区域  

  8. 深入理解java虚拟机-第六章

    第6章 类文件 6.3 Class类文件的结构 Class文件是一组以8位字节为基础单位的二进制流. Class文件格式采用一种类似C语言结构伪结构存储数据,这种伪结构中只有两种数据类型:无符号数和表 ...

  9. 《深入理解java虚拟机》第3版笔记3

    第3章 垃圾收集器与内存分配策略 可达性分析算法 在Java技术体系里面,固定可作为GC Roots的对象包括以下几种: 在虚拟机栈(栈帧中的本地变量表)中引用的对象,譬如各个线程被调用的方法堆栈中使 ...

  10. Linux内核分析第三章读书笔记

    第三章 进程管理 3.1 进程 进程就是处于执行期的程序 进程就是正在执行的程序代码的实时结果 线程:在进程中活动的对象.每个线程都拥有一个独立的程序计数器.进程栈和一组进程寄存器. 内核调度的对象是 ...

随机推荐

  1. 基于LZO的高性能无损数据解压缩IP

    LZOAccel-D LZO Data Decompression Core/无损数据解压缩IP Core LZOAccel-D是一个无损数据解压缩引擎的FPGA硬件实现,兼容LZO 2.10标准. ...

  2. 「浙江理工大学ACM入队200题系列」问题 H: 零基础学C/C++18——三位数反转

    本题是浙江理工大学ACM入队200题第二套中的H题 我们先来看一下这题的题面. 由于是比较靠前的题目,这里插一句.各位新ACMer朋友们,请一定要养成仔细耐心看题的习惯,尤其是要利用好输入和输出样例. ...

  3. 解决 net core 3.x 跨域问题

    跨域:指的是浏览器不能执行其他网站的脚本.它是由浏览器的同源策略造成的,是浏览器对javascript施加的安全限制. 以下几种情况是造成跨域的原因: 域名相同,端口不同 域名相同,协议不同(即,一个 ...

  4. 通过jmeter,将数据库数据查询出来并打印

    1. 连接数据库 1.1 添加jdbc驱动路径 1.2 JDBC Connection Configuration    1.3 JDBC Request 2. 查询数据库数据并打印 2.1 定义变量 ...

  5. 【网络】安装Nginx笔记

    目录 前言 安装前先更新下 安装依赖库 下载Nginx Nginx编译配置 编译&安装&验证nginx Nginx服务配置 配置SSL 参考 前言 up安装nginx主要是为了在服务器 ...

  6. Android Studio运行Failed to find Build Tools revision 30.0.3

    问题 第一次安装好Android Studio2022.5的版本之后开启虚拟机运行文件报错提示 Failed to find Build Tools revision 30.0.3 打开SDK已经安装 ...

  7. RNN的PyTorch实现

    官方实现 PyTorch已经实现了一个RNN类,就在torch.nn工具包中,通过torch.nn.RNN调用. 使用步骤: 实例化类: 将输入层向量和隐藏层向量初始状态值传给实例化后的对象,获得RN ...

  8. linux学习相关资料整理

    linux常用指令记录 Python3.9.9安装 supervisor安装与监控nginx 使用supervisor监控mysql supervisor监控tomcat配置文件 nginx-1.22 ...

  9. 使用PyLint分析评估代码质量

    什么是PyLint PyLint是一款用于评估Python代码质量的分析工具,它诞生于2003年,其最初十年的主要作者和维护者是Sylvain Thénault.PyLint可以用来检查代码是否错误. ...

  10. java顺序数组插入元素

    本文主要阐明已知顺序数组,在数组中插入一个数据元素,使其仍然保持有序. 关键是寻找num在原数组中插入的位置: 当num在原数组中是最大的情况,num应该插入到原数组的末尾. 否则,应该遍历原数组,通 ...