系列文章目录和关于我

前面《深入理解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. FastApi学习

    vscode配置 插件 code runner在 setting.json中关于python的修改为,因为我使用了虚拟环境,得让vscode找到python的路径 "code-runner. ...

  2. 思维分析逻辑 2 DAY

    目录 数据分析多元思维模型 微观能力 中观能力 宏观能力 电商平台分析 整体数据 漏斗模型 互联网金融分析 授信模型 了解芝麻信用分结构 数据源的数据变量 数据处理 游戏数据分析 常规指标 商业化指标 ...

  3. RSA、DSA 和 ECC 加密算法有什么区别?

    RSA.DSA 和 ECC 加密算法是用于在公钥基础设施中生成密钥的主要算法. 公钥基础设施 (PKI) 用于管理互联网通信和计算机网络中的身份和安全性. 启用 PKI 的核心技术是公钥密码术,这是一 ...

  4. VS使用web deploy发布到远程服务器

    如果是先安装 web deploy后安装iis的功能,需要在iis功能安装好后,修复下web deploy(直接运行web deploy的安装程序有修复)(本人也死在这里) 1.iis开启管理服务,和 ...

  5. 重学c#系列——逆变和协变[二十四]

    前言 简单整理一下逆变和协变. 正文 什么是逆变和协变呢? 首先逆变和协变都是术语. 协变表示能够使用比原始指定的派生类型的派生程度更大的类型. 逆变表示能够使用比原始指定的派生类型的派生程度更小的类 ...

  6. Sql Server日期转汉字字符串

    以下脚本转至互联网,增加了自己需要的功能并改成了函数的方式 SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON GO -- ================== ...

  7. 解决Emma中文乱码

    vim -/.emma/emmarc 找到 db_encoding=latin1 改为 db_encoding=utf8 然后重新运行emma,此时发现还是乱码,不要着急,在执行所有的sql语句之前加 ...

  8. node学习01

    1.前言 Node.js 是一个开源和跨平台的 JavaScript 运行时环境 Node.js 在浏览器之外运行 V8 JavaScript 引擎(Google Chrome 的内核). 这使得 N ...

  9. 基于 RocketMQ 的 Dubbo-go 通信新范式

    本文作者:郝洪范 ,Dubbo-go Committer,京东资深研发工程师. 一.MQ Request Reply特性介绍 什么是 RPC 通信? 如上图所示,类似于本地调用,A 服务响应调用 B ...

  10. C++编程笔记(智能指针学习)

    目录 scoped_ptr unique_ptr shared_ptr 智能指针简单应用 智能指针简单应用 scoped_ptr 拷贝构造和 =赋值操作均为私有,不允许 内部重载了解引用(*)操作符和 ...