垃圾收集器:引用计数算法
来源:互联网 发布:家庭防火知多少作文 编辑:程序博客网 时间:2024/05/21 11:24
引用计数算法作为垃圾收集器最早的算法,有其优势,也有其劣势,虽然现在的JVM都不再采用引用计数算法进行垃圾回收【例如Sun的Java hotspot采用了火车算法进行垃圾回收】,但这种算法也并未被淘汰,在著名的单进程高并发缓存Redis中依然采用这种算法来进行内存回收【后绪会以Redis作为例子,说明该算法】
什么是引用计数算法
直白一点,就是对于创建的每一个对象都有一个与之关联的计数器,这个计数器记录着该对象被使用的次数,垃圾收集器在进行垃圾回收时,对扫描到的每一个对象判断一下计数器是否等于0,若等于0,就会释放该对象占用的内存空间,同时将该对象引用的其他对象的计数器进行减一操作
两种实现方式
侵入式与非侵入性,引用计数算法的垃圾收集一般有侵入式与非侵入式两种,侵入式的实现就是将引用计数器直接根植在对象内部,用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通过使用引用计数器对内存进行回收的
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
- 垃圾收集器:引用计数算法
- 垃圾收集器:引用计数算法
- 垃圾收集器:引用计数算法
- jvm_垃圾收集器_引用计数算法
- 深入java虚拟机 - 垃圾收集 - 引用计数收集器
- 浅谈垃圾收集器与内存分配策略(引用计数算法)
- 引用计数与垃圾收集之比较
- 垃圾回收:引用计数与分代收集机制
- 垃圾收集算法和垃圾收集器
- 垃圾收集算法和垃圾收集器
- 垃圾收集算法与垃圾收集器
- 对象引用及垃圾收集算法
- 垃圾收集算法、垃圾回收算法、java垃圾收集器
- GC算法 垃圾收集器
- GC算法 垃圾收集器
- 垃圾收集器核心算法
- GC算法 垃圾收集器
- GC算法 垃圾收集器
- Android工程测试:命令码(在“拨号”应用中,输入命令码即可)
- 母函数与排列组合
- virsh console 无法连接到虚拟机
- Java 8新特性:lambda表达式(四)转载总结
- SSL证书
- 垃圾收集器:引用计数算法
- 将若干个字符串按字母顺序(由小到大)输出。(指针数组)
- No enclosing instance of type X is accessible. Must qualify the allocation with an enclosing
- FileZilla 425 Can't open data connection
- JDK 安装以及环境变量的配置(Windows)
- C++第一次复习笔记
- 在mini2440上移植Boa服务器
- RenderMonkey学习之熟悉软件
- 计蒜客 第19题:加一