这部分内容主要是为了稍后介绍各款垃圾收集器时做前置知识铺垫,如果对这部分内容感到枯燥或者疑惑,可以先放下看,等后续遇到要使用它们的实际场景、实际问题时再结合问题,再回来阅读和理解。
根节点枚举
我们都会说,垃圾回收时,遍历GC root,找到不可达对象,然后清掉。那么有没有想过 gc roots 集合怎么来?
可能有人会说,固定可作为GC Roots的节点, 不就是全局性的引用(例如常量或类静态属性)与执行上下文(例如栈帧中的本地变量表)中吗,我们不是已经知道GC Roots 是哪些了吗?
实际上, 尽管目标明确,但还是需要这么一个查找过程,再进一步,要做到查找过程高效也并非一件容易的事情。因为栈与寄存器都是无状态的,言外之意就是说GC 时垃圾收集器是不知道方法区中或栈中那一串数字到底是引用指针还是就是数字。(比如方法区中类属性或者执行上下文中存在很多不是引用类型的变量(int,boolean等),如果每次都遍历到这些,实际上是个无用功。另外,判断是不是引用这个逻辑也需要额外的开销)
这就有点类似,有个人,比如要到一个大村子去找一个手掌有一颗痣的人,尽管要找的那个人特征明显,而你要在这个大村里找,还是得一户一户人家的去找(遍历)。那么在这人群中。由于大村子的人数或者住户非常多,同理现在Java应用越做越庞大,光是方法区的大小就常有数百上千兆,里面的类、常量等更是恒河沙数,若要逐个检查是否是引用肯定得消耗不少时间。
什么是根节点枚举?查找 gc roots 集合的过程我理解就是根节点枚举做的事情。
既然按照上面的描述,查找gc roots 是个麻烦事,那 HotSpot VM设计者如何解决这个问题?
前面第一章的时候有提到,目前主流Java虚拟机使用的都是准确式垃圾收集,言外之意就是说能准确的知道哪些区域存储的是oop。怎么做到的呢?就是通过引入OopMap。其实就是一种空间换时间的方式。
这里的Oop是普通对象指针的意思:ordinary object pointer。这里的 map 也不是指对应的数据结构是 map,而是地图的意思。所以翻译过来就是普通对象指针地图,与现实很贴切。
一旦类加载动作完成的时候,HotSpot 就会把对象内什么偏移量上是什么类型的数据计算出来(生成对象引用的 OopMap 记录),在即时编译过程中,也会在特定的位置记录下栈里和寄存器里哪些位置是引用(生成OopMap 记录)。所以当用户线程停顿下来之后,其实并不需要一个不漏地检查完所有执行上下文和全局的引用位置。
迄今为止,所有收集器在根节点枚举这一步骤时都是必须暂停用户线程的,因此毫无疑问根节点枚举与之前提及的整理内存碎片一样会面临相似的“Stop The World”的困扰。
因为根节点枚举始终是必须在一个能保障一致性的快照中才得以进行——这里“一致性”的意思是整个枚举期间执行子系统看起来就像被冻结在某个时间点上,不会出现分析过程中,根节点集合的对象引用关系还在不断变化的情况,若这点不能满足的话,分析结果准确性也就无法保证。这是导致垃圾收集过程必须停顿所有用户线程的其中一个重要原因,即使是号称停顿时间可控。
我的疑问:根节点枚举并发时如何解决?
参考文章:https://zhuanlan.zhihu.com/p/286110609