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

最新文章

  1. [LeetCode] Unique Paths II 不同的路径之二
  2. android: Android Notification
  3. 【资源集合】94个iOS开发资源推荐,帮你加速应用开发
  4. python读取excel的行数
  5. Java中main函数参数String args[] 和 String[] args 区别
  6. [hackerrank]John and GCD list
  7. python内存管理
  8. change buffer
  9. 自写图片遮罩层放大功能jquery插件源代码,photobox.js 1.0版,不兼容IE6
  10. 自学JavaScript的几个例子
  11. 妙用Outlook2003群发商业邮件
  12. bzoj 4830: [Hnoi2017]抛硬币
  13. ●BZOJ 4237 稻草人
  14. Oracle 12C 密码文件问题 ORA-01017: invalid username/password; logon denied
  15. QT + OpenCV + MinGW 在windows下配置开发环境
  16. (转)PWA(Progressive Web App)渐进式Web应用程序
  17. Dubbo 暴露服务
  18. python_循环(迭代)
  19. 手机web不同屏幕字体大小高度自适应
  20. hibernate关联关系的crud之级联

热门文章

  1. 多态性   类(class)的四则运算
  2. JavaScript---Dom树详解,节点查找方式(直接(id,class,tag),间接(父子,兄弟)),节点操作(增删改查,赋值节点,替换节点,),节点属性操作(增删改查),节点文本的操作(增删改查),事件
  3. angular创建一个独立弹窗服务
  4. NGINX一览无余
  5. mysql修改密码策略
  6. Spring+SpringMVC+Hibernate 与 shiro 整合步骤
  7. python工程化最佳实践
  8. Fedora 29 安装 GitBook 教程
  9. (转!)MySql Host is blocked because of many connection errors; unblock with 'mysqladmin flush-hosts' 解决方法
  10. Tensorcore使用方法