浅谈垃圾回收机制
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)
缺陷
- 不能解决循环引用的问题(相互引用的对象引用计数都不是 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
缺陷
分代收集是目前比较好的垃圾回收方式