Loading

GC算法-引用计数法

概述

引用计数法又是什么鬼呢? 顾名思义, 对对象的引用进行计数. 通过记录每个对象被引用的次数, 来确定这个对象是否可以被回收.

实现

首先, 对对象的引用数量进行管理, 什么时候会更新呢?

  1. 创建对象: 新建一个对象(对这个新的对象引用数量+1)
  2. 更新指针: 将一个指向A对象的指针重新指向B对象(将A对象引用数量-1, B对象引用数量+1)

这次就不上代码了, 简单介绍一下思路就行. (我哥说代码看着费劲)

前提: 我们有一个全局的空闲地址链表: FREE_HEAD

创建对象的操作

  1. 从FREE_HEAD中寻找内存
  2. 若找到了, 该对象计数器置为1, 返回
  3. 若没有找到, 内存扩容, 返回1

更新指针的操作

  1. 将新的对象引用计数+1
  2. 将旧的对象引用计数-1. 若-1后引用数量为0, 则将该对象及所有的子对象添加到FREE_HEAD链表中.

实现说起来简简单单, 毕竟我也不用真的去实现, 简单想一下.

分析

在上一次的标记清除算法中, GC在每次内存不足时运行, 势必会导致程序暂停时间比较长. 但引用计数则在每次指针变更的同时进行管理, 在产生新的垃圾的时候立刻进行回收. 这就体现出它的几个优势了:

  1. 最大暂停时间短.
  2. 产生垃圾可立即回收

当然, 只说优势不说劣势都是扯犊子. 首先, 引用计数的优势也会成为它的劣势, 计数频繁的计算, 会拖累程序的速度. 而且每个对象都要开拓空间来保存引用数量. 当然了, 还有经常被说到的循环引用的问题. 等等吧.

  1. 频繁的更新引用计数拖累程序速度
  2. 每个对象需要开拓额外空间保存引用计数
  3. 循环引用对象无法被回收(就是A引用B, B引用A. 但是他们都没有被其他对象引用, 导致他俩的引用始终为1, 无法回收)

当然, 针对问题, 伟大的前人总是有办法去解决. 比如:

延迟计数法: 针对频繁更新计数器的问题而提出的. 大概意思就是不去实时的对引用数量进行更新, 将引用数量为0的记录到一个待处理的链表中, 当需要新的内存时再统一处理. 但是这样又会增大暂停时间, 才不要.

Sticky引用计数法: 引用计数通过额外的空间保存引用数量, 但这个必然会有最大值, 比如用1个字节, 则引用数量超过256的就记不下了. 这个方法对超出范围的处理方式很简单, 什么都不做, 不去回收, 毕竟被引用这么多次, 该对象定然很重要. 那这些对象不就永远都不能被回收了么? 可以, 等到没有内存了, 使用标记清除算法将所有对象过一遍.

当然, 针对引用计数法还有很多演变, 有些还是很有意思的, 有些是我看不懂的.


垃圾回收的整体思路分两个流派(我所知道的):

  1. 引用计数: 就是上面说的这种
  2. 可达性: 就是标记清除那种, 判断一个对象是否可以到达.

引用计数的最大优势应该就是不需要暂停程序去进行回收了, 随使用随回收. 但劣势也很明显: 需要计数器额外空间以及循环引用的问题.

个人是比较喜欢引用计数的, 实时性又高, 又不需要太多的额外空间. 只是需要在编写代码的时候刻意规避循环引用, 或者其他方法规避一下? 甚至不去处理都刻意, 如果只有少数的话(如果有很多, 还是换个算法吧).

posted @ 2020-04-05 18:37  烟草的香味  阅读(628)  评论(0编辑  收藏  举报