浅谈HotSpot根节点枚举

        与C语言相比,自动垃圾回收是Java语言的一个显著特点,所谓的垃圾就是程序运行期间已经没有用处的对象所占用的内存。注意,没有用处并不意味着没有被引用,因为也可能被其他“垃圾”所引用,从而形成“垃圾堆”。

        显然,如果一个对象在程序后续执行期间不会再被使用,我们就认为该对象已经“死亡”,那么JVM就应该对其占用的内存进行回收。因此我们需要去判断每个对象是否已经“死亡”。对于这个问题,最熟为人知的算法是引用计数法—— 为对象添加一个引用计数器,每有一次引用,就使其计数器加一,当引用失效时,计数器就减一;当计数器为0,意味着对象不再被使用,即死亡。这种算法十分简单,也就意味着可能存在很多例外,事实也确实如此,我们需要使用大量的额外处理才能保证该算法正常工作。比如上一段中提到的“垃圾堆”的例子,引用计数算法就无法回收他们。主流的JVM都没有使用引用计数法来管理内存。

        当前主流的商用程序语言的内存管理子系统,都是通过可达性分析算法来判断对象是否存活。我们将一个对象称为一个节点,该算法的基本思路就是把一些节点组成根节点(GC root)集合,从这些节点开始,根据引用关系搜索得到一个引用链(简单地说,A对象的成员属性是B,B对象的成员属性是C,则可以得到一个引用链 A->B->C)。如果某个对象没有存在于任何一个引用链中,显然这个对象就没有用处,应该进行回收。可以看出,该算法工作的前提是确定可以作为起始节点的对象。

        在Java技术体系中,固定可作为GC root的对象包括但不限于:

  1. 虚拟机栈的所有栈帧中引用的对象,通常是某一时刻的一些局部变量、方法参数等等、
  2. 类静态属性引用的对象,即类的静态变量
  3. 常量引用的对象,比如字符串常量池(String table)中的引用。
  4. JVM内部的引用,如基本数据类型对应的Class对象,一些常驻的异常对象等,还有系统类加载器。
  5. 所有被同步锁持有的对象

需要注意Class类型的类表示正在运行的Java程序的类和接口。枚举是一种类,每个数组也属于一个反映为类对象的类,拥有相同元素类型和维度的数组是属于同一个Class类的类对象。Java原始数据类型(boolean、byte、char、short、int、long、float、double)和关键字void也表示为类对象,也就是说基本数据类型也有对应的Class对象。但即便如此,基本数据类型没有类的全限定名,因此也无法调用方法。

        我们以可达性分析算法中从GC roots找引用链的过程也需要设计一个算法进行根节点枚举。固定可作为GC roots的节点主要是全局性的应用和执行的上下文(例如虚拟机栈中的本地变量表中的引用)。虽然目标明确,但实现查找过程的高效性并非易事。由于Java应用越做越大,光是方法区的大小就有上千兆,类、变量的数量数不胜数。若是逐个检查必然非常耗时。

        迄今为止,所有收集器在根节点枚举这一步骤时,无论时间长短,都必须暂停所有用户线程,才能保障一致性,避免出现枚举过程中根节点集合的引用关系还在不断变化的情况。当前主流的JVM使用的都是准确式的垃圾收集。虚拟机应当是有办法直接得到哪些地方存放对象引用。在HotSpot的解决方案中,使用一组OopMap的数据结构来实现该目的。一旦类加载动作完成,HotSpot就会把对象占用的内存中各个偏移量上对应是何种类型的数据计算出来,而且也会在特定位置记录栈中和寄存器中的哪些位置是引用。收集器可以直接得知这些信息,而不需要真的去检查整个方法区、虚拟机栈等。

  • 9
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值