龙空技术网

10年开发经验大牛用2000字带你搞懂GC算法分类及GC涉及的对象表示

大数据架构师 165

前言:

此刻兄弟们对“gc算法是什么意思”大约比较关怀,姐妹们都需要知道一些“gc算法是什么意思”的相关知识。那么小编同时在网络上收集了一些有关“gc算法是什么意思””的相关内容,希望各位老铁们能喜欢,小伙伴们一起来学习一下吧!

GC算法分类

垃圾回收中对象的标记一般有两种实现:引用计数(reference count)法和可达性分析(tracing)法(也称为根引用分析法、追踪式分析法)。

引用计数法指的是为每一个对象设计一个计数器,用于统计对象被引用的次数,如果对象引用次数为0,则表示没有任何引用就可以释放该对象。引用计数法实现简单,能立即回收无用内存。

引用计数通常在对象引用关系改变时修改引用值。算法伪代码如下:

void object_ref_mod(Object* obj, Object* field) {

inc_ref(filed); //注意引用计数需要先增后减,如果是先减后增,则可能出现bug

def_ref(obj);

obj= filed;

}

void inc_ref(Object* obj) {

obj->ref_count++;

}

void def_ref(Object* obj) {

obj->ref_count--;

if(obj->ref_count == 0) {

collect(obj); //释放对象占用的空间

//针对对象obj的成员变量依次遍历,只处理引用类型的成员变量

for(Object* ref_filed = obj->first_ref_field;

ref_field != NULL;

ref_field = ref_filed->next_ref_field) {

def_rec(ref_field);[1]

}

}

}

虽然引用计数法很简单,但是引用计数法也存在一些问题,主要有:

1)并发场景中,对象引用计数器的修改需要与对象引用关系的修改保持同步,这往往需要加锁实现或者使用非常复杂的无锁算法。

2)引用计数在对象回收时会引发链式反应,例如根对象的引用计数值为0,需要递归地将成员变量的引用值更新。同时对于满足回收条件的对象进行内存回收,所以回收时间可能不可控。

3)引用计数无法有效解决循环引用的问题,例如两个对象A和B相互引用,即使没有任何其他对象引用对象A和B,但对象A和B的引用值都为1,这会导致本应该释放的对象因为算法缺陷而无法回收。另外,有关研究表明,以Java为例,循环依赖的比例并不低,所以使用引用计数算法一般还需要辅以可达性分析法的垃圾回收算法。

虽然引用计数法存在这些缺点,但是因为其简单,在一些语言(如Python)中也有使用。

JVM采用的是可达性分析法。可达性分析法的基本思路就是通过根(root)作为起始点,从这些节点出发根据引用关系开始搜索,搜索所走过的路径称为引用链,当搜索完成后所有活跃对象都被识别,而一个对象没有被任何引用链访问到时,则证明此对象是不活跃的,可以被回收。示意图如图2-1所示。

图2-1中只有一个对象完全是死亡对象,当识别完活跃对象后,就可以知道哪个是不活跃对象。

图2-1 可达性分析法示意图

GC涉及的对象表示

在GC执行的过程中,如果发现对象位于引用链路中,就需要将对象进行标记。标记状态说明对象活跃,后续GC执行时根据标记状态移动活跃对象或者将不活跃对象回收。

另外,在GC的执行过程中可能会存在一种情况,即多个对象同时指向一个对象。对于这种情况,在移动式的GC算法中需要特别处理。如图2-2所示,

对象1和对象3都引用了对象2。

图2-2 多个对象引用一个对象

在移动式GC算法中,需要把对象1、对象2和对象3都移动到新的空间中,同时对象1、对象2和对象3都只能移动一次。由于对象1和对象3都指向对象2,因此在处理对象1和对象3的成员变量时,对象2可以被处理2次,但是只有一次是真正的转移对象,另外一次不能转移对象。

为了保证对象在GC过程中只移动一次,通常需要记录对象移动前后的映射关系,当对象尚未移动时可以移动对象,当对象已经移动,则直接使用移动后的对象,不需要再次移动对象。这说明在GC执行过程中需要记录额外的信息,记录信息的方式有多种,例如:可以为每一个对象分配额外的内存,该内存可以记录上述映射关系信息,也可以在标记时建立标记位图描述对象的活跃情况,在对象转移时使用转移信息表记录对象转移前后的地址信息。

这些实现信息记录的方法在JVM中都有体现,其本质和GC的实现有关。在早期的GC实现中倾向于将信息记录在对象头中,其主要原因是当访问对象时就可以获得相关信息,而不需要进行额外的内存访问;而最新的垃圾回收实现因为算法的复杂性,可能需要借助额外的数据结构才能保证GC的正确性。

前面提到JVM实现了OOP和Klass机制模拟运行时和编译时使用的对象和类。在前面已经介绍了Klass实例化对象的内存布局。这里简单地看一下Java对象在JVM内部的表示,如图2-3所示。

图2-3 JVM对象头示意图

根据JVM源码的注释,针对标记对象头信息在32位JVM中用32bit来描述,这32bit的组合使用情况如表2-1所示。

表2-1 对象头信息

另外,在源代码中还可以看到一个Promoted的状态,Promoted指的是对象从新生代晋升到老生代时,正常的情况下需要对这个对象头进行保存,主要原因是如果发生晋升失败,则需要重新恢复对象头。如果晋升成功,那么这个保存的对象头就没有意义。所以为了提高晋升失败时对象头的恢复效率,设计了promo_bits,这其实是重用了加锁位(包括偏向锁)。实际上只

有在以下3种情况下才需要保存对象头:

1)使用了偏向锁,并且偏向锁被设置(偏向锁在JDK 17中被移除,原因是在部分场景中使用偏向锁存在性能问题)。

2)对象被加锁。

3)为对象设置Hash_code。

值得一提的是,目前JVM正在优化对象的存储情况,因为额外的对象头实际上导致了内存的利用率降低。据有关论文研究,Java应用中的对象头大概浪费了5%~15%的内存空间。目前JVM提出了Lilliput(小人国项目)用于优化对象头额外的内存浪费,更多信息可以关注项目官网

本文给大家讲解的内容是Java虚拟机和垃圾回收基础知识:GC算法分类及GC涉及的对象表示下篇文章给大家讲解的内容是JVM中垃圾回收相关的基本知识:GC算法概述觉得文章不错的朋友可以转发此文关注小编;感谢大家的支持!

标签: #gc算法是什么意思