引用计数
一种直接的内存清扫方式,通过遍历可以直接的判定一个对象的存活性。
这个思想可以说是非常的简单,有新对象就增加新对象的引用计数,并且减少旧对象的引用计数,当某个引用计数的值为0的时候将它回收。
new(){ ref = allocate(); if (ref == NULL){ printf("error out of memory"); } rc(ref) = 0; return ref; } atomic write(src,i,ref){ addrefference(ref); deletereference(src[i]); src[i] = ref; } addrefference(ref){ if(ref != NULL){ rc(ref) = rc(ref) + 1; } } deletereference(ref){ if(ref != NULL){ rc(ref) = rc(ref) - 1; } if(rc(ref) == 0){ for each fld in Pointers(ref) deletereference(*fld); free(ref); } }
以上的代码很简单,相信大家都能看明白,有一点是write 这个操作需要保证是原子的,因为很多赋值器和回收器甚至会并发的执行。所以大家一定要做这个“屏障”操作。 引用计数算法的优势: 引用计数算法的回收几乎是很及时的无条件的,只要发现它的引用计数为0则就可以立即回收相关内存,尤其是在一个快要满的堆那是一种极速的回收方式。并且局部性较高。当系统发生部分不可用时也可以回收内存空间,例如分布式系统。 引用计数算法的缺点: @引用计数系统需要消耗额外的时间开销,由其很有可能对于寄存器以及线程栈也需要引用这样一个开销。 @引用计数的加减必须时原子化的,防止多线程竞争导致对象释放过早。 @只要请求了内存位置就极有可能污染高速缓存。 @无法处理环状的引用的数据结构,例如,孤岛状数据就很难释放。 @停顿时间较长,因为会递归的删出子节点,直接会导致瞬间停顿。 引用计数算法的改进点: #延时,延时引用计数。 #合并,有时候只需要关心初始和结束状态就可以。 #缓冲,缓冲所有引用计数的增建操作。 #效率低下的本质是,引用计数是全局特征之一,但是通常只有在(线程)局部状态下的操作才有效,以上的操作都是一致的即使“分开时段”操作。 延时引用计数: 只有当赋值器操作堆中对象产生的引用计数操作才会被执行,而栈上和寄存器产生的引用计数便更则会被延时执行。但是此时计数不再准确,需要引入“万物静止”的停顿来定期修正引用计数。
New(){
ref = allocate(); /*分配内存*/
if (ref == NULL){
collect();
ref = allocate();
if(ref == NULL){
printf("error out of memory");
}
}
rc(ref) = 0;
add(zct,ref);
return ref;
}
write(src,i,ref){ /*增加分配对象引用计数*/
if(src == roots)
src[i] = ref;
else
atomic /*如果不是根对象*/
addreference(ref); /*增加引用计数*/
remove(zct,ref); /*移出零计数表*/
deleteReferenceToZCT(src[i]); /*递减原来引用计数*/
src[i] = ref; /*建立引用计数关系*/
}
deleteReferenceToZCT(ref){
if(ref != NULL){
rc(ref) -= 1;
if(rc(ref) == 0){ /*引用计数为0时添加进0引用计数表*/
add(zct,ref);
}
}
}
atomic collect(){ /*回收*/
for each fld in roots /*如果是根节点,增加引用计数*/
addreference(*fld);
sweepZCT(); /*清空0引用表*/
for each fld in roots
deleteReferenceToZCT(*fld); /*恢复 */
}
sweepZCT(){
while(not isEmpty(zct))
ref = remove(zct);
if(rc(ref) == 0)
for each fld in Pointers(ref)
deleteReference(*fld);
free(ref);
}
这个思想我在来说说吧: @分配内存对象,将引用写入根的操作是无屏障的,但是将引用写入堆的时候必须引入屏障,在这种情况下,立即增加新对象的引用计数。当某一个引用计数为0时写屏障需要添加进到零引用表。 @当内存耗尽的时候,就必须进行垃圾回收,回收器需要挂起所有赋值器线程并且检查引用表中对象是否真正为0,只有当被一个或者多个根引用的时候,该对象才能被确定是激活的。 @new -> write ; while (collect == ture) do collect