内存管理器(二十四)引用计数

本文介绍了一种直接的内存管理方式——引用计数。通过增加和减少对象的引用计数来判断对象是否可以被回收。文章详细解释了引用计数算法的工作原理、优势和缺点,并提出了一些改进方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

引用计数

一种直接的内存清扫方式,通过遍历可以直接的判定一个对象的存活性。

这个思想可以说是非常的简单,有新对象就增加新对象的引用计数,并且减少旧对象的引用计数,当某个引用计数的值为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  
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值