基本概念

  • 在对象中引入计数器(无符号整数),用于记录有多少对象引用了该对象。
  • 通过增减计数器实现对内存的管理。
  • 分配对象时将计数器置1。
  • 更新引用时先对新指定的对象进行计数器加,而后才对旧对象进行减。
  • 在对计数器做减法时,判断其计数器是否等于0,等于0 表示为垃圾,即可进行回收。
  • 在更新引用时就进行了垃圾的标记与回收,因此STW会很短而且当对象变垃圾时能立马被回收。

优缺点

优点

  1. 即刻回收垃圾,在更改引用时就知道该对象是否为垃圾若是垃圾立马进行回收(但是该操作会占用用户线程的时间片)
  2. STW短,回收垃圾不需要遍历堆了。
  3. 不需要根据GC root遍历。

缺点

  1. 计数器值增减频繁。
  2. 计数器需要占用很多位。
  3. 实现繁琐,更新引用时很容易导致内存泄露。
  4. 循环引用无法回收(最重要的缺点)

改进

延迟引用计数法

针对计数器增减频繁

  • 从根引用的指针变化不修改计数器,为了使还存在引用的对象被回收,引入ZCT(Zero Count Table)记录计数器为0的对象(当为0时不直接删除而是加入到该表中)。
  • 分配块时,先正常分配(应该也是通过空闲链表),如果分配失败则从ZCT中找可以真正的垃圾对象进行回收与分配。
  • 扫描ZTC表需要遍历GC root,对所有GC root引用的计数器加1,而后遍历ZTC,此时计数器为0的即为垃圾,最后对GC root引用对象减一(还原)。

Sticky引用计数法

针对计数器占用很多位

由于大多数对象的引用并不会很多,可以减少计数器的位宽,当计数器溢出时有两种处理方式:

  1. 不管,对于溢出的对象不再增减计数器。此时无法判断是否为垃圾因此不再关此对象,溢出的可能性很低所以是一个可以考虑的解决办法。
  2. 使用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回收白色对象
posted on 2017-03-31 01:18  suolu  阅读(2242)  评论(0编辑  收藏  举报