浅谈垃圾回收机制

2019-04-19

年初开始学习 go 语言,目前作为一名 phper,自然会有很多比较,比如:它们的垃圾回收机制有何不同?这里记录下常见的 GC 算法,浅谈垃圾回收机制原理

常见 GC 算法

  • 引用计数法
  • Mark-Sweep 法(标记清除法)
  • 三色标记法
  • 分代收集法

引用计数法

原理

在每个对象内部维护一个整数值,叫做这个对象的引用计数
当对象被引用时引用计数加一,当对象不被引用时引用计数减一,当引用计数为 0 时,自动销毁对象

谁在使用

php 就使用这种垃圾回收机制

    每个 php 变量存在一个叫 "zval" 的变量容器中
    一个 zval 变量容器,除了包含变量的类型和值,还包括两个字节的额外信息
        is_ref      bool    标识这个变量是否是属于引用集合
        refcount    int     表示指向这个 zval 变量容器的变量(也称符号即 symbol)个数

代码示例

$a = 'new string';
xdebug_debug_zval('a');
// 输出 a: (refcount=1, is_ref=0)string 'new string' (length=10)

PHP5和PHP7的垃圾回收机制有什么不同
一看就懂系列之 由浅入深聊一聊php的垃圾回收机制

缺陷

  • 不能解决循环引用的问题(相互引用的对象引用计数都不是 0 ,因此永远不能被收集)
  • 每次对象的赋值都要将引用计数加一,增加了消耗

循环引用是指对象 A 和对象 B 互相持有对方的引用

Mark-Sweep 法(标记清除法)

原理

从程序的根节点开始,递归地遍历所有对象,将能遍历到的对象打上标记
所有未标记的的对象当作垃圾销毁

谁在使用

golang 1.5 以前使用的这个算法

缺陷

STW 问题(Stop The World)
    在标记时必须暂停整个程序
    否则其他线程的代码可能会改变对象状态,从而可能把不应该回收的对象当做垃圾收集掉

三色标记法

原理

是传统 Mark-Sweep 的一个改进,它是一个并发的 GC 算法

1. 首先创建三个集合:白、灰、黑
2. 将所有对象放入白色集合中
3. 然后从根节点开始遍历所有对象(注意这里并不递归遍历),把遍历到的对象从白色集合放入灰色集合
4. 之后遍历灰色集合,将灰色对象引用的对象从白色集合放入灰色集合,之后将此灰色对象放入黑色集合
5. 重复 4 直到灰色中无任何对象
6. 通过 write-barrier 检测对象有变化,重复以上操作
7. 收集所有白色对象(垃圾)

在程序执行的同时进行收集,并不需要暂停整个程序

谁在使用

Go 1.5、Go 1.6

缺陷

可能程序中的垃圾产生的速度会大于垃圾收集的速度,这样会导致程序中的垃圾越来越多无法被收集掉

分代收集法

原理

也是传统 Mark-Sweep 的一个改进
这个算法是基于一个经验:绝大多数对象的生命周期都很短

1. 一般 GC 都会分三代
    在 java 中称之为新生代(Young Generation)、年老代(Tenured Generation)和永久代(Permanent Generation)
    在 .NET 中称之为第 0 代、第 1 代和第 2 代
2. 新对象放入第 0 代
3. 当内存用量超过一个较小的阈值时,触发第 0 代收集
4. 第 0 代幸存的对象(未被收集)放入第 1 代
5. 只有当内存用量超过一个较高的阈值时,才会触发第 1 代收集
6. 第 2 代同理

因为第 0 代中的对象十分少,所以每次收集时遍历都会非常快(比第 1 代收集快几个数量级)
只有内存消耗过于大的时候才会触发较慢的第 1 代和第 2 代收集

谁在使用

使用的语言(平台)有 jvm、.NET 

缺陷

分代收集是目前比较好的垃圾回收方式