垃圾收集器:引用计数算法

引用计数算法作为垃圾收集器最早的算法,有其优势,也有其劣势,虽然现在的JVM都不再采用引用计数算法进行垃圾回收【例如SunJava hotspot采用了火车算法进行垃圾回收】,但这种算法也并未被淘汰,在著名的单进程高并发缓存Redis中依然采用这种算法来进行内存回收【后绪会以Redis作为例子,说明该算法】

什么是引用计数算法

直白一点,就是对于创建的每一个对象都有一个与之关联的计数器,这个计数器记录着该对象被使用的次数,垃圾收集器在进行垃圾回收时,对扫描到的每一个对象判断一下计数器是否等于0,若等于0,就会释放该对象占用的内存空间,同时将该对象引用的其他对象的计数器进行减一操作

两种实现方式

侵入式与非侵入性,引用计数算法的垃圾收集一般有侵入式与非侵入式两种,侵入式的实现就是将引用计数器直接根植在对象内部,用C++的思想进行解释就是,在对象的构造或者拷贝构造中进行加一操作,在对象的析构中进行减一操作,非侵入式恩想就是有一块单独的内存区域,用作引用计数器

算法的优点

使用引用计数器,内存回收可以穿插在程序的运行中,在程序运行中,当发现某一对象的引用计数器为0时,可以立即对该对象所占用的内存空间进行回收,这种方式可以避免FULL GC时带来的程序暂停,如果读过Redis 1.0的源码,可以发现Redis中就是在引用计数器为0时,对内存进行了回收

算法的劣势

采用引用计数器进行垃圾回收,最大的缺点就是不能解决循环引用的问题,例如一个父对象持有一个子对象的引用,子对象也持有父对象的引用,这种情况下,父子对象将一直存在于JVM的堆中,无法进行回收,代码示例如下所示(引用计数器无法对a与b对象进行回收):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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通过使用引用计数器对内存进行回收的

1
2
3
4
5
6
7
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

1
2
3
4
5
6
7
8
9
10
11
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操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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

http://www.cnblogs.com/WJ5888/p/4359783.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值