基本概念
- 在对象中引入计数器(无符号整数),用于记录有多少对象引用了该对象。
- 通过增减计数器实现对内存的管理。
- 分配对象时将计数器置1。
- 更新引用时先对新指定的对象进行计数器加,而后才对旧对象进行减。
- 在对计数器做减法时,判断其计数器是否等于0,等于0 表示为垃圾,即可进行回收。
- 在更新引用时就进行了垃圾的标记与回收,因此STW会很短而且当对象变垃圾时能立马被回收。
优缺点
优点
- 即刻回收垃圾,在更改引用时就知道该对象是否为垃圾若是垃圾立马进行回收(但是该操作会占用用户线程的时间片)
- STW短,回收垃圾不需要遍历堆了。
- 不需要根据GC root遍历。
缺点
- 计数器值增减频繁。
- 计数器需要占用很多位。
- 实现繁琐,更新引用时很容易导致内存泄露。
- 循环引用无法回收(最重要的缺点)
改进
延迟引用计数法
针对计数器增减频繁
- 从根引用的指针变化不修改计数器,为了使还存在引用的对象被回收,引入ZCT(Zero Count Table)记录计数器为0的对象(当为0时不直接删除而是加入到该表中)。
- 分配块时,先正常分配(应该也是通过空闲链表),如果分配失败则从ZCT中找可以真正的垃圾对象进行回收与分配。
- 扫描ZTC表需要遍历GC root,对所有GC root引用的计数器加1,而后遍历ZTC,此时计数器为0的即为垃圾,最后对GC root引用对象减一(还原)。
Sticky引用计数法
针对计数器占用很多位
由于大多数对象的引用并不会很多,可以减少计数器的位宽,当计数器溢出时有两种处理方式:
- 不管,对于溢出的对象不再增减计数器。此时无法判断是否为垃圾因此不再关此对象,溢出的可能性很低所以是一个可以考虑的解决办法。
- 使用GC标记-清除算法,先把所有对象(我觉得处理已溢出的就可以?)的计数器置0,而后遍历GC root,可引用的将计数器置1,清除阶段则清理那些计数器为0的。
1位引用计数法
Sticky的极端例子
- 计数器只有1位,0表示只有一个引用(UNIQUE)1表示多引用(MULTIPLE)。
- 更新引用时通过复制某个指针来更新。
觉得有问题,怎么找到可以被复制的指针呢?指针是单链表,无法通过对象找到其指针吧
部分标记-清除算法
针对循环引用
- 为了解决循环引用不能被回收有采用在某些时候加入GC标记-清除算法,然而循环引用往往很少,效率很低。
- 该方法是只对可能存在循环引用的对象群进行Sweep。
- 对象被标记为四种颜色,黑:不是垃圾,白:垃圾,灰:搜索过的对象,阴影:可能是循环引用对象。上色采用两位标记
- 当删除引用关系时,先自减,而后如果计数器为0则回收该对象,否则将该对象置为阴影并加入到hatch_queue
- 当空闲链表无法分配时将去hatch_queue中扫描是否存在循环垃圾可进行回收。
- 遍历取hatch_queue中对象分别做paint_gray、scan_gray和collect_white
- paint_gray将对象置灰,而后将所有子对象计数器减1,并调用迭代paint_gray.
- scan_gray将灰色中计数器为0的对象置白,大于0的涂成黑色。
- collect_white回收白色对象