Python
python每个对象都会有一个引用计数变量,当引用计数为0时,会被回收。使用了引用计数的地方,就会存在循环引用。
Python 为了解决这个问题,在虚拟机中引入了一个双向链表,把所有对象都放到这个链表里。Python 的每个对象头上都有一个名为 PyGC_Head 的结构。
在这个结构里,gc_next 和 gc_prev 的作用就是把对象关联到链表里。而 gc_refs 则是用于消除循环引用的。当链表中的对象达到一定数目时,Python 的 GC 模块就会执行一次标记清除。具体来讲,一共有四步。
- 将 ob_refcnt 的值复制到 gc_refs 中。
- 遍历整个链表,对每个对象,将它直接引用的对象的 gc_refs 的值减一。
- 将 gc_refs 值为 0 的对象,从对象链表中摘下来,放入一个名为“临时不可达”的链表中。
- 以可达对象链表中的对象为根开始深度优先搜索,将所有访问到 gc_refs 为 0 的对象,再从临时不可达链表中移回可达链表中。最后留在临时不可达链表中的对象,就是真正的垃圾对象了。
Go
Go 语言采用的垃圾回收算法是并发标记清理(Concurrent Mark Sweep,CMS)算法。
那为什么 Go 语言还是选择了 CMS 作为其 GC 算法呢?这里原因主要有两点:
- Go 语言通过内存分配策略缓解了 CMS 容易产生内存碎片的缺陷。
- Go 语言是有值类型数据的,即 struct 类型。有了值类型的介入,编译器只需要关注函数内部的逃逸分析(intraprocedural escape analysis),而不用关注函数间的逃逸分析(interprocedural analysis),由此可以将生命周期很短的对象安排在栈上分配。
Go 语言通过采用 TCMalloc 的分配器思路,以及对内存对象类别大小的精确控制,保障了程序在运行过程中能够有高速的分配效率,以及维持较低的碎片率,这样回收器就可以采用相对简单的 CMS 算法。
TCMalloc 会给每个线程分配一个 Thread-Local Cache,对于每个线程的分配请求,就可以从自己的 Thread-Local Cache 区间来进行分配。此时因为不会涉及多线程操作,所以并不需要进行加锁,从而减少了因为锁竞争而引起的性能损耗。
此文章为7月Day24学习笔记,内容来源于极客时间《编程高手必学的内存知识》