引用计数算法做为垃圾收集器最先的算法,有其优点,也有其劣势,虽然如今的JVM都再也不采用引用计数算法进行垃圾回收【例如Sun的Java hotspot采用了火车算法进行垃圾回收】,但这种算法也并未被淘汰,在著名的单进程高并发缓存Redis中依然采用这种算法来进行内存回收【后绪会以Redis做为例子,说明该算法】java
什么是引用计数算法git
直白一点,就是对于建立的每个对象都有一个与之关联的计数器,这个计数器记录着该对象被使用的次数,垃圾收集器在进行垃圾回收时,对扫描到的每个对象判断一下计数器是否等于0,若等于0,就会释放该对象占用的内存空间,同时将该对象引用的其余对象的计数器进行减一操做github
两种实现方式redis
侵入式与非侵入性,引用计数算法的垃圾收集通常有侵入式与非侵入式两种,侵入式的实现就是将引用计数器直接根植在对象内部,用C++的思想进行解释就是,在对象的构造或者拷贝构造中进行加一操做,在对象的析构中进行减一操做,非侵入式恩想就是有一块单独的内存区域,用做引用计数器算法
算法的优势缓存
使用引用计数器,内存回收能够穿插在程序的运行中,在程序运行中,当发现某一对象的引用计数器为0时,能够当即对该对象所占用的内存空间进行回收,这种方式能够避免FULL GC时带来的程序暂停,若是读过Redis 1.0的源码,能够发现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 1.0经过使用引用计数器对内存进行回收的this
typedef struct redisObject { unsigned type:4; unsigned encoding:4; unsigned lru:REDIS_LRU_BITS; /* lru time (relative to server.lruclock) */ int refcount;//引用计数器 void *ptr;//指向实际的对象空间 } robj;
Redis中全部的操做,操做的都是robj这个结构体,在这个结构中存放着对象的引用计数器refcount,以下是建立对象的代码,在这个建立对象的过程当中,将引用计数器置为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; /* Set the LRU to the current lruclock (minutes resolution). */ o->lru = LRU_CLOCK(); return o; }
如下操做是对引用计数器进行+1操做
robj *createStringObjectFromLongLong(long long value) { robj *o; if (value >= 0 && value < REDIS_SHARED_INTEGERS) { //对共享池中常量对象的引用计数+1 incrRefCount(shared.integers[value]); o = shared.integers[value]; } else { if (value >= LONG_MIN && value <= LONG_MAX) { o = createObject(REDIS_STRING, NULL); o->encoding = REDIS_ENCODING_INT; o->ptr = (void*)((long)value); } else { o = createObject(REDIS_STRING,sdsfromlonglong(value)); } } return o; }
Redis中有一个共享池,共享池中的变量,通常不会轻易释放,大部份对象均可以对这部份常量进行共享,共享一次,对应对象robj中的引用计数器进行一次+1操做
如下是进行-1操做
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); } else { o->refcount--; } }
从上面的代码中能够看出,对对象的引用计数器进行-1操做时,若是对象的引用计数器变为0时,会调用相应类型的释放函数,释放对象的内存空间,若是对象的引用计数器的值大于1时,直接对对象的引用计数器进行减1操做,而后返回
从上面的代码能够看出,Redis中经过对对象的引用计数器进行减1操做,能够实如今程序运行过程当中,回收对象所占用的内存空间,固然Redis中还有LRU算法,实现内存淘汰策略,待之后再分析
Redis 1.0源码注解:https://github.com/zwjlpeng/Redis_Deep_Read