1、通过OopMap完成根节点枚举

HotSpot虚拟机使用可达性分析算法确定对象是否可以被GC。

可达性分析算法从一系列GCRoot对象开始,向下搜索引用链,如果一个对象没有与任何GCRoot对象关联,这个对象就会被判定为可回收对象。

GCRoot包括以下对象:

  1. 虚拟机栈上的本地变量表引用的对象

  2. 方法区中类的静态属性引用的对象

  3. 方法区中常量引用的对象

  4. 本地方法栈中JNI引用的对象

这一过程称为根节点枚举,也就是垃圾回收中的标记过程,当前所有的垃圾收集器,在标记阶段都必须停止所有java执行线程(STW),以保证对象引用状态不会发生变化。

HotSpot虚拟机作为准确式虚拟机,维护了一个专门的映射表(OopMap)记录哪些位置存放着对象引用,来快速完成根节点枚举过程。

类加载完成,HotSpot就会把对象内某个偏移位置是否为对象引用记录下来,JIT编译过程中,也会在特定的位置记录下栈和局部变量表中哪些位置是引用。

2、安全点SafePoint

为每一个操作记录OopMap不现实,HotSpot虚拟机引入了SafePoint。安全点就是某些记录线程此时调用栈、寄存器等一些重要的数据区域里什么地方包含了GC要管理的指针(对象引用),而这些对象引用是通过OopMap结构进行记录的,可以直接通过对OopMap结构的访问来获得对象的引用。

SafePoint是程序中的某些位置,线程执行到这些位置时,线程中的某些状态是确定的,在safePoint可以记录OopMap信息,线程在safePoint停顿,虚拟机进行GC。一个线程可以在SafePoint上,也可以不在SafePoint上。一个线程在SafePoint时,它的状态可以安全地被其他JVM线程所操作和观测。

线程停顿方式有两种,抢先式中断和主动式中断:

  1. 抢先式中断:虚拟机需要GC时,中断所有线程,让没有到达SafePoint的线程继续执行至SafePoint并中断

  2. 主动式中断:虚拟机不直接中断线程,而是在内存中设置标志位,线程检查到标志位被设置,运行至SafePoint时主动中断

hotspot采用的是第一种, 也就是主动检测的方式. 而在主动检测的方式中又分为两种方式:

  1. 指定点执行检测代码
  2. polling page访问异常触发

Hotspot, 顾名思义, 就是热点的意思, 这里所谓的热点指的是热点代码, 也就是执行频率很高的代码, hotspot会根据运行时的信息来统计, 并将高频率执行的java字节码直接翻译成本地代码, 由此提高执行效率. 因此, hotspot有两种执行方式, 一个是解释执行, 一个是编译执行。指定点检测主要是解释执行用的,对于需要高效实现的地方,则采用polling page。

polling page和普通物理页面没什么区别,需要safepoint时, 会修改该页面的权限为不可访问, 这样编译的代码在访问这个页面时, 会触发段违规异常(SEGEV). 而hotspot在启动时捕获了这个异常, 当意识到是访问polling page导致时, 则主动挂起。

SafePoint一般出现在以下位置:

  1. 循环体的结尾

  2. 方法返回前

  3. 调用方法的call之后

  4. 抛出异常的位置

这些位置保证线程不会长时间运行而无法到达SafePoint,避免其他线程都停顿等待本线程。

public static void main(String[] args) throws Exception {
     [1]DemoObject demoObject = new DemoObject();
     [2]//往demoObject上挂一个字符串对象
     [3]demoObject.val1 = "this is a string object";
     [4]Thread.sleep(1000000);
}

我们知道代码是在线程里执行的, GC的代码也是在线程里执行, 如果执行GC的时候其他线程也同时执行的话, heap的状态将是难以追踪的. 以上面的代码为例, 假设GC线程通过扫描线程的stack(线程stack是一种GC Root), 扫描到demoObject, 然后根据这时候, main函数执行到[3], 但还未执行, 扫描的结果只发现demoObject是存活的, 接下来, main函数的线程执行[3], demoObject.val1引用了一个字符串对象, 这个对象的扫描就漏掉了, 除非以某种方式记录下这个变化, 然后重新扫描demoObject. 即便有办法记录这个赋值导致的变化然后再次扫描, 如果其他线程这时候又来捣乱, 那么重新扫描的时候有可能又发生了变化, 陷入循环…

再往下一点, 我们知道CPU执行运算时的数据, 需要从内存里载入寄存器中, 运算完再从寄存器存入内存, 对象的地址也要经过这么个过程. 假如一个java线程分配了一个对象A, 该对象的地址存在某个寄存器中, 然后线程的cpu时间片到期被切换出去, 同时GC的线程开始扫描存活对象, 由于没有路径到这个地址还在寄存器中的对象, 这个对象被认为是garbage, 回收了. 然后睡眠的java线程醒来了, 把寄存器中的对象地址赋值给了存活对象的某个字段, over…

GC的目的在于帮助我们收集不再使用的内存, 但是把正在是使用的内存当成垃圾回收显然是不能接受的. 同时通过分析也看到, 由于多线程运行环境的存在, GC的工作会变的异常复杂, 要安全的回收垃圾, 需要具备两个条件:

  • heap的变化是受限的, 当然了, 所有线程都停下来最好, 这样heap 在GC过程中是稳定的,这是最简单的情况.

  • heap的状态是已知的, 不会有活着的对象找不到或者很难找的情况. 想想对象地址在寄存器中的情况, 虽然可以有办法可以扫描线程的寄存器, 即使这样, 也必须知道哪个寄存器在某个时刻存的是地址, 要做到扫描不漏是很复杂的事情.

3、安全区SafeRegion

SafePoint无法解决线程未达到SafePoint并处于休眠或等待状态的情况,此时引入SafeRegion的概念。

SafeRegion是代码中的一块区域或线程的状态,在SafeRegion中,线程执行与否不会影响对象引用的状态。线程进入SafeRegion会给自己加标记,告诉虚拟机可以进行GC;线程准备离开SafeRegion前会询问虚拟机GC是否完成。

参考文章:

(1)聊聊JVM(六)理解JVM的safepoint https://blog.csdn.net/ITer_ZC/article/details/41847887

(2)聊聊JVM(九)理解进入safepoint时如何让Java线程全部阻塞 https://blog.csdn.net/ITer_ZC/article/details/41892567

hotspot的安全区(saferegion)和安全点(safepoint)的更多相关文章

  1. 虚拟机研究系列-「GC本质底层机制」SafePoint的深入分析和底层原理探究指南

    SafePoint前提介绍 在高度优化的现代JVM里,Safepoint有几种不同的用法.GC safepoint是最常见.大家听说得最多的,但还有deoptimization safepoint也很 ...

  2. JVM(四)垃圾回收的实现算法和执行细节

    全文共 1890 个字,读完大约需要 6 分钟. 上一篇我们讲了垃圾标记的一些实现细节和经典算法,而本文将系统的讲解一下垃圾回收的经典算法,和Hotspot虚拟机执行垃圾回收的一些实现细节,比如安全点 ...

  3. JVM自动内存管理:对象判定和回收算法

    可回收对象的判断方法 1.引用计数算法 2.可达性分析算法 引用计数算法 给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1:当引用失效时,计数器值就减1:任何时刻计数器为0的对象就是 ...

  4. 没有二十年功力,写不出Thread.sleep(0)这一行“看似无用”的代码!

    你好呀,我是喜提七天居家隔离的歪歪. 这篇文章要从一个奇怪的注释说起,就是下面这张图: 我们可以不用管具体的代码逻辑,只是单单看这个 for 循环. 在循环里面,专门有个变量 j,来记录当前循环次数. ...

  5. 接口偶尔超时,竟又是JVM停顿的锅!

    原创:扣钉日记(微信公众号ID:codelogs),欢迎分享,转载请保留出处. 简介 继上次我们JVM停顿十几秒的问题解决后,我们系统终于稳定了,再也不会无故重启了! 这是之前的文章:耗时几个月,终于 ...

  6. HotSpot的算法实现

    1.枚举根节点 可达性分析中从GC Roots节点找引用,可作为GC Roots的节点主要是全局性的引用与执行上下文中,如果要逐个检查引用,必然消耗时间.另外可达性分析对执行时间的敏感还体现在GC停顿 ...

  7. Hotspot GC实现原理

    GC扫描 可达性分析的GC Roots主要是全局性引用或在Stack Frame中 ,现在的应用仅仅方法区往往就有几百兆,这样要这个检查这里面的引用,就必然会消耗很多时间,效率很低. 分析工作在一个保 ...

  8. 5.HotSpot的算法实现

    1.枚举根节点 在可达性分析中,可以作为GC Roots的节点有很多,但是现在很多应用仅仅方法区就有上百MB,如果逐个检查的话,效率就会变得不可接受. 而且,可达性分析必须在一个一致性的快照中进行-即 ...

  9. [Inside HotSpot] C1编译器工作流程及中间表示

    1. C1编译器线程 C1编译器(aka Client Compiler)的代码位于hotspot\share\c1.C1编译线程(C1 CompilerThread)会阻塞在任务队列,当发现队列有编 ...

随机推荐

  1. Java自学-接口与继承 隐藏

    Java中的方法隐藏 与重写类似,方法的重写是子类覆盖父类的对象方法 隐藏,就是子类覆盖父类的类方法 步骤 1 : 父类 父类有一个类方法 :battleWin package charactor; ...

  2. 使用Python搭建http服务器

    David Wheeler有一句名言:“计算机科学中的任何问题,都可以通过加上另一层间接的中间层解决.”为了提高Python网络服务的可移植性,Python社区在PEP 333中提出了Web服务器网关 ...

  3. iOS架构:MVVM设计模式+RAC响应式编程

    https://cloud.tencent.com/developer/article/1117009 一:为什么要用MVVM? 为什么要用MVVM?只是因为它不会让我时常懵逼. 每次做完项目过后,都 ...

  4. 【总结】web工作代码分类整理(持续更新)

    文件.blob 文件下载失败,将Blob对象转换为Json,处理异常错误? 使用FileReader,核心代码: var reader = new FileReader() reader.onload ...

  5. Elasticsearch使用DateHistogram聚合

    date_histogram是按照时间来构建集合(桶)Buckts的,当我们需要按照时间进行做一些数据统计的时候,就可以使用它来进行时间维度上构建指标分析.     在前面几篇中我们用到的hitogr ...

  6. 微信小程序 子组件调用父组件方法

    原文连接   --->  https://blog.csdn.net/qq_40190624/article/details/87972265 组件 js:  var value = 123; ...

  7. pecl安装php扩展

    Pecl全称The PHP Extension Community Library,php社区扩展库,由社区编写,维护. 使用pecl方便之处在于我们不用到处找源码包下载编译,配置,不用手动phpiz ...

  8. Ueditor 自动设置上传图片的宽度或高度

    Uedior在上传图片的生活,需要自动设置上传图片的宽度或高度属性.该方法只能用于多图上传组件,单图上传无法使用. 该方法基于 ueditor 1.4.3 版本制作: 1.添加属性字段,在config ...

  9. Kotlin匿名函数与闭包详解

    Lambda表达式实例演练: 继续先来编写一些Lambda表达式相关的代码: 接下来想从上面的字符串数组中找到带有"h"的字符串并打印出来: 如果学习了Java8的Lambda表达 ...

  10. selenium入门知识

    自动化测试 重复测试.性能测试.压力测试 快速.可靠.可重复.可程序化.广泛的 自动化测试适合场合 回归测试.更多更频繁的测试.手工测试无法实现的工作.跨平台产品的测试.重复性很强的操作 不适合场合 ...