GC的前置工作,聊聊GC是如何快速枚举根节点的

这篇具有很好参考价值的文章主要介绍了GC的前置工作,聊聊GC是如何快速枚举根节点的。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

本文已收录至GitHub,推荐阅读 👉 Java随想录

微信公众号:Java随想录

原创不易,注重版权。转载请注明原作者和原文链接

目录
  • 什么是根节点枚举
  • 根节点枚举存在的问题
  • 如何解决根节点枚举的问题
  • 安全点
  • 安全区域

上篇文章中我们留下了个坑:「根节点枚举」,这篇文章就把坑填上。

在上篇文章中我们知道了HotSpot使用的是可达性分析算法,该算法需要进行根节点枚举。

但是查找根节点枚举的过程要做到高效并非一件容易的事情,现在Java应用越做越庞大,光是方法区的大小就常有数百上千兆,里面的类、常量等更是「恒河沙数」(一种修辞手法),若要逐个检查以这里为起源的引用肯定得消耗不少时间。

大家可以思考下,如果你是JVM的开发者,你会怎么去做?

前面的文章大伙可能有点忘了,那么首先我们对根节点枚举,先做个复习(我绝对不是在混字数)。

什么是根节点枚举

顾名思义,根节点枚举就是找出所有的GC Roots

当然要成为GC Roots是有条件的,固定可作为GC Roots的对象包括以下几种(摘抄自《深入理解虚拟机 第3版》):

  • 在虚拟机栈(栈帧中的本地变量表)中引用的对象,譬如各个线程被调用的方法堆栈中使用到的参数、局部变量、临时变量等。
  • 在方法区中常量引用的对象,譬如字符串常量池(String Table)里的引用。
  • 在本地方法栈中JNI(即通常所说的Native方法)引用的对象。
  • Java虚拟机内部的引用,如基本数据类型对应的Class对象,一些常驻的异常对象(比如NullPointExcepiton、OutOfMemoryError)等,还有系统类加载器。
  • 所有被同步锁(synchronized关键字)持有的对象。
  • 反映Java虚拟机内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等。

上面说的这些,大伙肯定记不住,反正总结就一句话:固定可作为GC Roots的节点主要在全局性的引用(例如常量或类静态属性)与执行上下文(例如栈帧中的本地变量表)中。

根节点枚举存在的问题

迄今为止,所有收集器在根节点枚举这一步骤时都是必须暂停用户线程的。因此毫无疑问根节点枚举与之前提及的整理内存碎片一样会面临相似的「Stop The World」的困扰。

根节点枚举必须在一个能保障一致性的快照中才得以进行——这里「一致性」的意思是整个枚举期间执行子系统看起来就像被冻结在某个时间点上。

为什么要这么做?

试想一下,你妈给你打扫房间,你妈一边打扫,你一边丢垃圾,房间永远也打扫不干净。

所以本质上来说,根节点枚举遇到的问题,就是并发问题。

如果不「冻结」的话,根节点集合的对象引用关系在不断变化,那么分析结果准确性也就无法保证。

所以即使是号称停顿时间可控,或者(几乎)不会发生停顿的CMS、G1、ZGC等收集器,在枚举根节点这一步也是必须要停顿的。

弄明白问题之后,我们开动脑筋想想怎么解决。

如何解决根节点枚举的问题

目前主流Java虚拟机使用的都是「准确式垃圾收集」。

所谓准确式垃圾收集是指垃圾收集器能够精确地确定内存中哪些区域被对象引用,哪些区域已经不再使用,并且可以立即回收不再使用的内存。

在准确式垃圾收集中,垃圾收集器需要知道每一个引用类型变量(包括实例字段、静态字段、本地变量和输入参数等)在内存中的确切位置,以及这个位置是否正在被引用。

这样,当垃圾收集器需要进行回收时,它就可以精确地找到并回收那些不再有任何引用的对象所占用的内存。

相对应的,还有一种叫做「保守式垃圾收集」,它不能精确地识别所有的引用,只能保守地认为所有看起来像对象引用的值都可能是引用。这种方式可能会导致某些实际上可以被回收的内存得不到回收。

HotSpot采用的是准确式垃圾收集

所以当用户线程停顿下来之后,其实并不需要一个不漏地检查完所有执行上下文和全局的引用位置,虚拟机应当是有办法直接得到哪些地方存放着对象引用的。

在HotSpot的解决方案里,是使用一组称为OopMap的数据结构来达到这个目的。OopMap可以理解为就是映射表,存储栈上的对象引用的信息,这是一种空间换时间的做法。

在 GC Roots 枚举时,只需要遍历每个栈桢的 OopMap,通过 OopMap 存储的信息,快捷地找到 GC Roots,这样就不需要进行全局扫描。

用大白话说,其实就是用类似映射表这种手段记录下来引用关系,时不时去更新下映射表,然后根节点枚举只需要扫描映射表就知道哪些地方存放引用了,而不用去进行全局扫描。

OK,弄明白之后,问题又来了,既然OopMap是一个映射表,这个表什么时候被更新?

你可能会觉得这有啥难的,引用更新的时候同步去更新映射表不就完事了吗,然而事情并没有想的那么简单。

要知道引用关系变化是十分频繁的,如果引用每变化一次就更新对应的OopMap,那将会需要大量的额外存储空间,这样垃圾收集伴随而来的空间成本就会变得无法忍受的高昂。

安全点

解决这个问题的办法就是「安全点」,事实上,只是在「特定的位置」记录了这些信息,这些位置被称为安全点(Safepoint)。

因此GC不是随时随地来的,得到达安全点时才可以开始GC。

所以流程我们就清楚了:先是到达安全点,然后更新OopMp,然后进行根节点枚举,找到GC Roots,开始GC。

安全点的选举,一般会在如下几个位置出现:

  • 循环的末尾
  • 方法临返回前
  • 调用方法之后
  • 抛异常的位置

到这里为止,貌似问题我们都解决了,but,还有一个问题我们需要考虑,我们前面说了系统要在某个时间点处于「冻结」状态,那么如何在垃圾收集发生时让所有线程都跑到最近的安全点,然后停顿下来?

有两种方案可供选择:抢先式中断(Preemptive Suspension)主动式中断(Voluntary Suspension)

  • 「抢先式中断」:不需要线程的执行代码主动去配合,在垃圾收集发生时,系统首先把所有用户线程全部中断,如果发现有用户线程中断的地方不在安全点上,就恢复这条线程执行,让它一会再重新中断,直到跑到安全点上。现在几乎没有虚拟机实现采用抢先式中断来暂停线程响应GC事件。
  • 「主动式中断」:当垃圾收集需要中断线程的时候,不直接对线程操作,仅仅简单地设置一个标志位,各个线程执行过程时会不停地主动去轮询这个标志,一旦发现中断标志为真时就自己在最近的安全点上主动中断挂起。

安全点的设计似乎已经完美解决如何停顿用户线程,但是仍然有问题,安全点机制保证了程序执行时,在不太长的时间内就会遇到可进入垃圾收集过程的安全点。但是,程序「不执行」的时候呢?

所谓的程序不执行就是没有分配处理器时间,典型的场景便是用户线程处于Sleep状态或者Blocked状态,这时候线程无法响应虚拟机的中断请求,不能再走到安全的地方去中断挂起自己。

对于这种情况,JVM引入安全区域(Safe Region)来解决。

安全区域

安全区域是指能够确保在某一段代码片段之中,引用关系不会发生变化。因此,在这个区域中任意地方开始垃圾收集都是安全的。我们也可以把安全区域看作被扩展拉伸了的安全点。

当用户线程执行到安全区域里面的代码时,首先会标识自己已经进入了安全区域。

那样当这段时间里虚拟机要发起垃圾收集时就不必去管这些已声明自己在安全区域内的线程了。

当线程要离开安全区域时,它要检查虚拟机是否已经完成了根节点枚举(或者垃圾收集过程中其他需要暂停用户线程的阶段)。

如果完成了,那线程就当作没事发生过,继续执行;否则它就必须一直等待,直到收到可以离开安全区域的信号为止。

好了,本篇文章到这结束咯。结合上篇可达性分析,我们一步步揭开了GC的神秘面纱,然而路还很远,仍然还有很多东西是需要我们去学习了解的。


感谢阅读,如果本篇文章有任何错误和建议,欢迎给我留言指正。

老铁们,关注我的微信公众号「Java 随想录」,专注分享Java技术干货,文章持续更新,可以关注公众号第一时间阅读。

一起交流学习,期待与你共同进步!文章来源地址https://www.toymoban.com/news/detail-661953.html

到了这里,关于GC的前置工作,聊聊GC是如何快速枚举根节点的的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【spring源码系列-04】注解方式启动spring时refresh的前置工作

    Spring源码系列整体栏目 内容 链接地址 【一】spring源码整体概述 https://blog.csdn.net/zhenghuishengq/article/details/130940885 【二】通过refresh方法剖析IOC的整体流程 https://blog.csdn.net/zhenghuishengq/article/details/131003428 【三】xml配置文件启动spring时refresh的前置工作 https://blog.csdn.net/zhenghuishen

    2024年02月08日
    浏览(28)
  • 软件工程:左移策略,决策和工作要前置,是提效的关键

    hi,我是熵减,见字如面。 在软件开发的中,你是否也遇到过类似的场景: 团队的目标是在1个月内,开发出一款新的社交媒体应用程序。由于时间比较紧,任务重,所以在开发的初期,方案设计、质量保障和安全性等上面,都是未能充分的考虑,以此来节省开发成本,并保

    2023年04月25日
    浏览(33)
  • 程序实现--对外接口可不仅仅是“给大佬递餐”,前置工作还是要做滴~

    先看下面代码中的两个方法。 execute → doPaymentAuthResultQuery ,一个方法接收到参数后,直接将参数原样传递给另一个方法。 View Code 然后,我们把这段代码稍作改动,主要是变更了第二个被调方法 doPaymentAuthResultQuery 的参数。大家来比较一下,改动前后,哪个更优一些。 View

    2024年02月08日
    浏览(29)
  • 程序实现--对外接口可不仅仅是“给大佬递餐”,前置工作还是要做滴

    先看下面代码中的两个方法。 execute → doPaymentAuthResultQuery ,一个方法接收到参数后,直接将参数原样传递给另一个方法。 View Code 然后,我们把这段代码稍作改动,主要是变更了第二个被调方法 doPaymentAuthResultQuery 的参数。大家来比较一下,改动前后,哪个更优一些。 View

    2024年02月08日
    浏览(26)
  • 边缘计算节点BEC典型实践:如何快速上手PC-Farm服务器?

    百度智能云边缘计算节点BEC(Baidu Edge Computing)基于运营商边缘节点和网络构建,一站式提供靠近终端用户的弹性计算资源。边缘计算节点在海外覆盖五大洲,在国内覆盖全国七大区、三大运营商。BEC通过就近计算和处理,大幅度优化响应时延、降低中心带宽成本。 通过创建

    2024年02月12日
    浏览(31)
  • 【spring源码系列-03】xml配置文件启动spring时refresh的前置工作

    Spring源码系列整体栏目 内容 链接地址 【一】spring源码整体概述 https://blog.csdn.net/zhenghuishengq/article/details/130940885 【二】通过refresh方法剖析IOC的整体流程 https://blog.csdn.net/zhenghuishengq/article/details/131003428 【三】xml配置文件启动spring时refresh的前置工作 https://blog.csdn.net/zhenghuishen

    2024年02月08日
    浏览(38)
  • 程序设计---对外接口可不仅仅是“给大佬递餐”,前置工作还是要做滴~

    先看下面代码中的两个方法。 execute → doPaymentAuthResultQuery ,一个方法接收到参数后,直接将参数原样传递给另一个方法。 View Code 然后,我们把这段代码稍作改动,主要是变更了第二个被调方法 doPaymentAuthResultQuery 的参数。大家来比较一下,改动前后,哪个更优一些。 View

    2024年02月05日
    浏览(30)
  • 入职从零到一:如何快速学习Git以适应工作环境

    本文并非面向完全的 Git 初学者,也不会详细介绍每一个 Git 命令和它的所有选项。相反,本文的目标读者是那些已经有一些基础,至少知道如何在本地仓库进行基本的版本控制操作,包括 git add , git commit 和 git log ,但是还没有在企业环境中真正使用 Git 进行过项目开发的开

    2024年02月11日
    浏览(57)
  • 深入理解JAVA垃圾收集器CMS,G1工作流程原理 GC流程图 什么社会触发Minor GC?触发MinorGC过程。Full GC 过程。

    JVM内存空间基础知识点(基于JDk1.8) 1.方法区:逻辑概念,元空间,方法区主要用于存储类的信息、常量池、方法数据、方法代码等。方法区逻辑上属于堆的一部分,但是为了与堆进行区分,通常又叫“非堆”。 2.程序计数器:程序计数器可以看作当前线程所执行的字节码的

    2024年04月25日
    浏览(29)
  • VS Code 快速消除前置空格和常用快捷键

    目录 介绍: 消除前置空格:SHIFT+TAB 常用的 VS Code 快捷键 介绍: 在使用 Visual Studio Code (VS Code) 进行代码编辑时,熟练掌握一些快捷键和编辑技巧可以大幅提高开发效率。本文将重点介绍如何使用快捷键 SHIFT+TAB 快速消除代码块的前置空格,并分享一些其他常用的快捷键。 消

    2024年02月09日
    浏览(26)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包