垃圾回收 - 引用计数法

GC原本是一种"释放怎么都无法被引用的对象的机制"。那么人们自然而然就会想到,可以让所有对象事先记录下"有多少程序引用了自己"。让各对象知道自己的"人气指数",从而让没有人气的对象自己消失,这就是引用计数法。

1、计数器

计数器表示的是对象的人气指数,也就是有多少程序引用了这个对象。计数器是无符号的整数

1.1计数器的增减

在GC-标记清除算法的其他算法中,没有分块时mutator会调用下面这样的函数,启动GC分配空闲的内存空间。

clike 复制代码
garbage_collect(){
	.....
}

然而在引用计数法中并没有mutator明确启动GC的语句,引用计数法与mutator的执行密切相关,他在mutator的处理过程中通过增减计数器的值来进行内存管理。这涉及到new_obj()函数和update_ptr()函数

1.2 new_obj()函数

clike 复制代码
new_obj(size){
	obj = pickup_chunk(size,$free_list)
	if(obj == NULL) allocation_fail()
	else
		obj.ref_cnt = 1
		return obj
}

1.3 update_ptr()函数

该函数用于更新指针ptr,使其指向对象obj,同时进行计数器值的增减。

clike 复制代码
update_ptr(ptr,obj){
	inc_ref_cnt(obj)
	dec_ref_cnt(*ptr)
	*ptr = obj
}

inc_ref_cnt(obj){
	obj.ref_cnt ++;
}

dec_ref_cnt(obj){
	obj.ref_cnt --;
	if(obj.ref_cnt == 0)
		for(child : children(obj))
			dec_ref_cnt(*child)
		reclaim(obj)
}

该程序具体进行的是以下2项操作:

  • 对指针ptr新引用的对象obj的计数器进行增量操作
  • 对指针ptr之前引用的对象*ptr的计数器进行减量操作

在变更数组元素等的时候会进行指针的更新。通过更新指针,可能会产生没有被任何程序引用的的垃圾对象。引用计数法会监督在更新指针的时候是否有产生垃圾,从而在产生垃圾的时候立即将其回收。

2、优缺点

2.1优点

  • 可即刻回收垃圾
  • 最大暂停时间短
  • 没有必要沿着指针查找。

2.2缺点

  • 计数器值的增减处理繁重
  • 计数器需要占用很多位
  • 实现繁琐复杂
  • 循环引用无法回收

3、延迟引用计数法

因为引用计数法中计数器值的增减处理繁重。于是就产生了延迟引用计数法。计数器增减处理繁重的原因之一是从根的引用变化频繁,因此我们就让从根引用的指针的变换不反映在计数器上。比如我们把重写全局变量指针的update_ptr($ptr,obj)改写成*ptr = obj。

这样一来,即使频繁重写堆中对象的引用关系,对象的计数器值也不会有所变化。但是这样内存管理还是不能顺利进行。因为引用没有反应在计数器上,所以各个对象的计数器没有正确表示出对象本身被引用数。于是就有可能发生对象仍在活动但却被错当成垃圾回收的情况。

于是,我们在延迟引用计数法中使用ZCT(Zero Count Table),他会事先记录下计数器值在dec_ref_cnt()函数作用下变为0的对象。

因为计数器值为0的对象不一定都是垃圾,所以暂时先将这些对象保留。

优点:通过延迟减轻了因根引用频繁发生变化导致的计数器增减所带来的额外负担。

缺点:为了延迟计数器的增减,垃圾不能立马回收,导致垃圾压迫成堆,我们也就失去了引用计数法的优点-可即刻回收垃圾。

4、Sticky引用计数法

在引用计数法中,我们有必要研究一件事,那就是要为计数器设置多大的位宽,假设为了反映所有引用,计数器需要一个字(32位机器就是32位)的空间。但是这样回大量消耗内存空间。比如2个字的对象就要附加一个字的计数器,也就是说计数器害的对象所占空间增大了1.5倍

对此我们有个方法,那就是用来减少计数器位宽的"Sticky引用计数法"。举个例子,我们假设用于计数器的位数为5位,那么这种计数器最多只能数到2的5次方减1,就是31个引用数。如果此对象被大于31个引用对象引用,那么计数器就会溢出。针对计数器溢出,需要暂停堆计数器的管理,对付这种对象,我们主要有两种方法:

4.1、什么都不做

对于计数器溢出的对象,我们可以这样处理:不再增减计数器的值,就把它放着。但是这样一来,即使这个对象成了垃圾(引用次数为0)也不能将其回收,这样就白白浪费了内存空间。

4.2、使用标记清除算法处理

在适当的时候使用标记清除算法来充当引用计数法的后援。这样做的优点是在计数器溢出后即使成了垃圾,程序还是能够回收它,并且还可以回收循环的垃圾。

5、1位引用计数法

1位引用计数法是Stick的一个极端例子,就是计数器的大小只有一位,瞬间就会溢出。

6、部分标记清除法

之前已经讲过,引用计数法存在的一大问题就是不能回收循环的垃圾。这是引用计数法的一大特色,用GC标记清除算法就不会有这种问题。那么我们自然会想到,只要跟之前使用延迟引用计数法时一样,利用GC 标记清除算法不就好了吗?也就是说,可以采用一般情况下执行引用计数法,在某个时刻启动 GC 标记清除算法的方法。

6.1前提

在部分标记清除算法中,对象会被涂成4种颜色来进行管理:

颜色 含义 表示
绝对不是垃圾的对象(对象产生时的初始颜色) 00
绝对是垃圾的颜色 01
搜索完毕的对象 10
阴影 可能是循环垃圾的对象 11
相关推荐
MavenTalk几秒前
Move开发语言在区块链的开发与应用
开发语言·python·rust·区块链·solidity·move
v'sir9 分钟前
POI word转pdf乱码问题处理
java·spring boot·后端·pdf·word
提高记忆力18 分钟前
SpringBoot整合FreeMarker生成word表格文件
java·spring
JDS_DIJ19 分钟前
RabbitMQ
java·rabbitmq·java-rabbitmq
XiaoLeisj33 分钟前
【JavaEE初阶 — 多线程】生产消费模型 & 阻塞队列
java·开发语言·java-ee
王佑辉33 分钟前
【jvm】如何判断一个对象是否可以回收
jvm
2401_8401922736 分钟前
python基础大杂烩
linux·开发语言·python
@东辰40 分钟前
【golang-技巧】- 定时任务 - cron
开发语言·golang·cron
hxj..41 分钟前
【设计模式】外观模式
java·设计模式·外观模式
机器人天才一号42 分钟前
C#从入门到放弃
开发语言·c#