对象引用计数器
来源:互联网 发布:网络购物平台图片大全 编辑:程序博客网 时间:2024/06/05 06:29
引用计数算法作为垃圾收集器最早的算法,有其优势,也有其劣势,虽然现在的JVM都不再采用引用计数算法进行垃圾回收【例如Sun的Java hotspot采用了火车算法进行垃圾回收】,但这种算法也并未被淘汰,在著名的单进程高并发缓存Redis中依然采用这种算法来进行内存回收【后绪会以Redis作为例子,说明该算法】
什么是引用计数算法
直白一点,就是对于创建的每一个对象都有一个与之关联的计数器,这个计数器记录着该对象被使用的次数,垃圾收集器在进行垃圾回收时,对扫描到的每一个对象判断一下计数器是否等于0,若等于0,就会释放该对象占用的内存空间,同时将该对象引用的其他对象的计数器进行减一操作
详细介绍大家请移步到 百度百科-引用计数
两种实现方式
侵入式与非侵入性,引用计数算法的垃圾收集一般有侵入式与非侵入式两种,侵入式的实现就是将引用计数器直接根植在对象内部,用C++的思想进行解释就是,在对象的构造或者拷贝构造中进行加一操作,在对象的析构中进行减一操作,非侵入式恩想就是有一块单独的内存区域,用作引用计数器
算法的优点
使用引用计数器,内存回收可以穿插在程序的运行中,在程序运行中,当发现某一对象的引用计数器为0时,可以立即对该对象所占用的内存空间进行回收,这种方式可以避免FULL GC时带来的程序暂停,如果读过Redis的源码,可以发现Redis中就是在引用计数器为0时,对内存进行了回收。
算法的劣势
采用引用计数器进行垃圾回收,最大的缺点就是不能解决循环引用的问题,例如一个父对象持有一个子对象的引用,子对象也持有父对象的引用,这种情况下,父子对象将一直存在于JVM的堆中,无法进行回收,代码示例如下所示(引用计数器无法对a与b对象进行回收):
class A { private B b; public B getB() { return b; } public void setB(B b) { this.b = b; }}class B { private A a; public A getA() { return a; } public void setA(A a) { this.a = a; }}public class Test { public static void main(String[] args) { A a = new A(); B b = new B(); a.setB(b); b.setA(a); }}
以上内容摘录自 垃圾收集器:引用计数算法。
Redis 源码例子
为了更清楚的理解对象引用计数器,我们来看看《PHP核心技术与最佳实践》一书中对 redis 源码的分析:
在redis中,设想以下场景:一个客户端调用get命令获取一个较大的key时(不能通过一次网络I/O把数据传输完毕),另一个客户端调用del命令删除此key,如果此时没有对key进行任何保护,get操作就有可能导致内存段错误(因为del操作已经把key从内存删除,而get操作还在进行,这样get操作就会访问到非法内存地址)。
为了解决这个问题,Redis使用对象引用计数器。原理是:给对象添加一个引用计数器,每当有地方引用它时,计数器值就加 1,当引用失效时,计数器值就减 1 ,当引用计数器值为 0 时,Redis便把此对象从内存中删除。
引用计数器巧妙的解决了get命令和del命令的冲突问题,原理 如下图所述:
要理解对象引用计数器,先了解Redis的对象。在Redis中,所有的key和value都是通过对象(Redis Object)进行存储的,对象的结构体定义如下:
typedef struct redisObject { unsigned type:4; ubsigned storage:2; unsigned encoding:4; unsigned lru:22; int refcount; //引用计数器 void *ptr; //指向实际的对象空间} robj;
robj 结构体中的 ptr 字段指向数据的内存地址,refcount 字段就是引用计数器。下面看看 Redis 怎样通过引用计数器解决上面的问题。
当 Redis 创建一个对象时,把对象的引用计数器初始化为 1,代码如下:
robj *createObject(int type, void *ptr) { robj *o = zmalloc(sizeof(*o)); o->type = type; o->encoding = REDIS_ENCODING_RAW; o->ptr = ptr; //创建时将引用计数器初始为1 o->refcount = 1; o->lru = server.lruclock; o->storage = REDIS_VM_MEMORY; return o;}
当客户端调用 get 命令获取一个 key 时,调用 incrRefCount 函数把对象的引用计数器加 1,incrRefCount 函数代码如下:
void incrRefCount(robj *o){ o -> refcount ++;}
所以,只有一个客户端使用get命令获取key时,此 key 的对象引用计数器应该为2。通过 “object refcount (key)”命令查看一个 key 的引用计数器。
当客户端调用 del 命令时,Redis 调用 decrRefCount 函数把对象的引用计数器减 1 ,如果对象的引用计数器等于 0 ,Redis才把对象从内存中删除。decrRefCount 函数代码如下:
void decrRefCount(robj *o) { if (o->refcount <= 0) redisPanic("decrRefCount against refcount <= 0"); if (o->refcount == 1) { switch(o->type) { case REDIS_STRING: freeStringObject(o); break; case REDIS_LIST: freeListObject(o); break; case REDIS_SET: freeSetObject(o); break; case REDIS_ZSET: freeZsetObject(o); break; case REDIS_HASH: freeHashObject(o); break; default: redisPanic("Unknown object type"); break; } zfree(o); //通过zmollac申请内存,通过zfree释放内存 } else { o->refcount--; }}
从上面的分析可知,使用对象引用计数器后,当 get 操作还没完成时调用 del 命令也不会出现内存段错误。这是因为调用 get 命令后对象的引用计数器加 1 ,所以此时调用 del 命令也不会使其引用计数器变为 0(不会从内存中删除此 key)。
调用 del 命令后在什么时候才将这个 key 从内存中删除呢?答案是等待所有的 get 操作完成后(如果同时有多个客户端调用 get 命令,就要等待所有客户端完成后)。当一个 get 操作完成后,Redis 把此 key 的引用计数器减 1。而当所有的get操作都完成后,此 key 的引用计数器将变为 0,此时,Redis 就把这个 key 从内存中删除。
这部分内容摘录自《PHP核心技术与最佳实践》。
- 对象引用计数器
- 对象的创建与引用计数器实现
- 引用计数器
- 引用计数器
- cocos2d正确创建和释放对象,引用计数器的使用
- Python的引用计数器
- reference counter 引用计数器
- 初学c++引用计数器
- MRC环境 引用计数器
- string奇怪引用计数器
- C++ 引用计数器
- 第十二篇:OC中的对象内存管理都是对 对象引用计数器 进行管理
- GDI泄露终极解决方案——HOOK API,建立GDI对象引用计数器
- 性能计数器对象
- 使用计数器跟踪对象
- 黑马程序员_引用计数器
- Objective-C - 自动引用计数器
- 【C++】引用计数器简单示例
- css3中scale的效果
- 读书笔记-人月神话6
- JS中2个感叹号的作用
- PHP常见9大缓存
- Satisfactory Pairs
- 对象引用计数器
- 109-Convert Sorted List to Binary Search Tree
- 使用模块定义文件(.def)文件生成dll
- MySQL最新解压缩版安装[5.7.18]
- 111- Minimum Depth of Binary Tree
- IIC协议
- 跳转到系统设置页面
- 112- Path Sum
- 114- Flatten Binary Tree to Linked List