GC(二)
原创文章,转载请注明出处
《垃圾回收的算法与实现》笔记
引用计数法
- 计数器表示的是对象的人气指数,也就是有多少程序引入了这个对象(被引用数)。计数器是无符号的整数,用于计数器的位数根据算法和实现有所不同。
就是对于创建的每一个对象都有一个与之关联的计数器,这个计数器记录着该对象被使用的次数,垃圾收集器在进行垃圾回收时,对扫描到的每一个对象判断一下计数器是否等于0,若等于0,就会释放该对象占用的内存空间,同时将该对象引用的其他对象的计数器进行减一操作。
引用计数法的优缺点
优点
-
可即刻回收垃圾
在引用计数法中,每个对象始终都知道自己的被引用数(计数器的值),当值为 0 时,对象会马上把自己作为空闲空间连接到空闲链表。
-
最大暂停时间短
只有当通过 mutator 更新指针时程序才会执行垃圾回收。也就是说,每次执行 mutator 生成垃圾时这部分垃圾都会被回收,因而大幅度地削减了 mutator 的最大暂停时间。
-
没有必要沿指针查找
沿各个节点查找的话,成本就会增加。
缺点
-
计数器值的增减处理繁重
大多数情况下,指针都会频繁的更新,特别是有根的指针。
-
计数器需要占用很多位
用于引用计数的计数器最大必须能输完堆中所有对象的引用数。
-
实现繁琐复杂
进行指针更新操作的 update_ptr() 函数是在 mutator 这边调用的。而调用这个函数的地方很多,所以重写过程中很容易出现遗漏。如果漏掉了某处,内存管理就无法正确进行,就会产生 BUG 。
-
循环引用无法回收
循环垃圾的产生 :当两个对象互相引用,但是并没有被其他对象引用。所以这两个对象就无法被回收。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19//定义 Person 类
class Person {
String name
Person lover
}
//实例化对象
taro = new Person("太郎")
hanako = new Person("花子")
//太郎喜欢花子
taro.lover = hanako
//花子喜欢太郎
hanako.lover = taro
//将 taro 转换为 null
taro = null
//将 hanako 转换为 null
hanako = null
- 循环引用无法回收这种方式一方面无法区分软、虛、弱、强引用类别。另一方面,会造成死锁,假设两个对象相互引用始终无法释放counter,永远不能GC。
延迟引用计数法(Deferred Reference Counting)
- 为了改善引用计数法的一个缺点(计数器值的增减处理繁重)而改进的办法。
缺点 :计数器值的增减频繁的原因是“从根的引用变化频繁“
改进 :让从根引用的指针的变化不反映在计数器上
延迟引用计数法中使用 ZCT (Zero Count Table)。 ZCT 是一个表,它会事先记录下计数器值在 dec_ref_cnt() 函数的作用下变为 0 的对象。
计数器值为 0 的对象不一定都是垃圾,所以暂时先将这些对象保留,所以需要修正 dec_ref_cnt() 函数,使其适应延迟引用计数法。
dec_ref_cnt() 函数
1 |
dec_ref_cnt() { |
延迟引用计数法的优缺点
优点
- 减轻了因根引用频繁发生变化而导致的计数器增减所带来的额外负担。
缺点
- 在此方法中,程序延迟了根引用的计数器,垃圾会压迫堆,失去了延迟计数法的一个优点——即刻回收垃圾。
Sticky 引用计数法
-
用来减少计数器位宽的方法。
假设用于计数器的位数是 5,此方法最多只能数到 2 的 5 次方减 1,也就是 31 个引用数。
1 位引用计数法
- 1 位引用计数法 (1 bit Reference Counting) 是 Sticky 引用计数法的一个极端例子。
用 0 表示被引用数为 1,用 1 表示被引用数大于等于 2,这样能有效率的进行内存管理。
1 位引用计数法的优缺点
优点
- 不容易出现高速缓存缺失(需要缓存的数据不再缓存里)。
缺点
- 计数器溢出对象。