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. ASP.NET 后台 COOKIE 的设置

    这几年经常与安全打交道,深知 COOKIE 对一个网站的安全影响有多大,所以在编写相与 cookie 相关代码的时候,都会特别的小心. 最近做一个系统,有几个地方用到 cookie, 然后统一把 co ...

  2. 查看Linux内核版本

    您可能因多种原因需要确切知道GNU / Linux操作系统上运行的内核版本. 也许您正在调试与硬件相关的问题,或者了解影响旧内核版本的新安全漏洞,并且您想知道您的内核是否易受攻击. 无论是什么原因,从 ...

  3. delphi安装控件

    设置路径,要不就自己把文件copy到对应的文件夹里面再给你装一下装控件的 http://www.cppfans.com/vclcontrol.asp有关 VCL 控件的常见问题 (安装.卸载.升级BC ...

  4. 面试题:栈的push和pop序列是否一致

    参数是两个数组,arr1,arr2 stack stack = new Stack() for(int i=0;j=0;i<arr1.length;i++){ stack.push(arr[i] ...

  5. CSS 样式的使用方式、选择器

    在html中使用css的三种方式: 1.行内样式:同过元素的style属性来设置 <p style="font-size:20px; color:red">hello& ...

  6. 一、MySQL基础知识

    一.背景介绍 我们每天都在访问各种网站.APP,如微信.QQ.抖音,今日头条等,这些东西上面都存在大量的信息,这些信息都需要有地方存储,存储在哪里呢?数据库. 所有我们需要开发一个网站.APP,数据库 ...

  7. sqlserver建表及注释

    --**********************************************创建表************************************************* ...

  8. JavaScript基础内容中的函数详解

    函数 函数:即方法 函数就是一段预先设置的功能代码块,可以反复调用,根据输入参数的不同,返回不同的值. 为什么使用函数: 1.方便调用 2.代码重用,利于维护 3.便于修改,便于重构 4.简化逻辑,利 ...

  9. Flask-Script Manager

    Flask Script扩展提供向Flask插入外部脚本的功能,包括运行一个开发用的服务器,一个定制的Python shell,设置数据库的脚本,cronjobs,及其他运行在web应用之外的命令行任 ...

  10. Java基础--线程创建方式

    线程的创建主要有两种形式,通过继承Thread或者实现Runnable接口,本质上没有太大区别. /** * @date: 2019/7/16 **/ public class ThreadOne i ...