引用計數算法做爲垃圾收集器最先的算法,有其優點,也有其劣勢,雖然如今的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