引用计数算法
- 导语
- 什么是引用计数算法
- 两种实现方式
- 算法优点
- 算法劣势
导语
引用计数算法作为垃圾收集器最早的算法,只需要一个简单的递归就可以实现,有一定优势,也有一定的劣势。JVM不再采用引用计数算法进行垃圾回收,但是这种算法并未被淘汰,在著名的Redis中依然使用。
什么是引用计数算法
对于创建的每一个对象都有一个相关联的计数器,这个计数器记录着该对象被引用的次数,垃圾收集器进行垃圾回收时,对扫描到的每个对象都要判断下该对象计数器是否为0,若为0就会释放该对象的内存空间。这里要注意:遍历该对象属性引用相应的计数都要减一。
伪代码实现
new():分配内存
ref <- allocate()
if ref == null
error "out of memory"
rc(ref) <- 0 //将ref的引用计数(reference counting) 设置为0
return ref
atomic write(dest,ref) //更新对象的引用
addReference(ref)
deleteReference(dest)
dest <- ref
addReference(ref):
if ref != null
rc(ref) <- rc(ref)+1
deleteReference(ref):
if ref != null
rc(ref) <- rc(ref)-1
if rc(ref) == 0 //如果当前ref的应用计数为0,则表明其将被回收
for each fld in Pointers(ref)
deleteReference(*fld)
free(ref) //释放ref的内存空间
Java代码示例
String p = new String("abc")
String q = new String("def")
p = q
当我们执行代码p=q时,实际上相当于调用了伪代码中的write(p,q)
方法,即对p原先指向的对象要进行deleteReference()
操作-引用计数减一,原因是p变量不在指向该对象。对于q引用的对象要进行addReference()
操作-引用计数加一。
两种实现方式
侵入式与非侵入式,引用计数算法的垃圾收集一般分为侵入式和非侵入式。侵入式的实现方式是将引用计数器直接根植在对象内部,用C++的思想进行解释是,在对象的构造或者拷贝构造中进行加一操作,在对象的析构中进行减一操作,非侵入式思想就是有一块单独的内存空间用于对象的引用计数器。
算法优点
使用引用计数器,内存回收可以穿插在程序中运行,在程序运行中,但发现某一对象的引用计数器为0时,可以立即对该对象所占的内存空间进行回收,这种方式可以避免FULL GC带来的程序暂停。
算法劣势
采用引用计数算法进行垃圾回收,最大的缺点是不能解决循环引用的问题。例如一个父对象持有一个子对象的引用,子对象也持有父对象的引用,这种情况下父子对象将会一直存储在JVM堆中,无法进行回收。