引用计数
Python 语言默认采用的是 引用计数 的垃圾回收机制。 该算法的原理是:每个对象维护一个 ob_ref 的字段,用来记录该对象当前被引用的次数,每当有新的引用指向该对象是,它的引用计数 ob_ref + 1,每当该对象的引用失效时,计数器 ob_ref - 1,一旦引用计数为 0,该对象立即被回收,对象占用的空间会被释放。它的缺点是需要额外的空间维护引用计数,这个问题是其次的,最主要的问题是它不能解决对象中的 “循环引用” 问题,因此,很多语言比如 java 并没有采用才算法来做垃圾的收集机制。
什么是循环-引用?A 和 B 相互引用而在外部没有引用 A B 中的任何一个,它们的引用计数虽然都为 1,但显然应该被回收:
a = {} # 对象 a 的引用计数为 1
b = {} # 对象 b 的引用计数为 1
a['b'] = b # 对象 b 的引用计数 +1 = 2
b['a'] = a # 对象 a 的引用计数 +1 = 2
del a # a 的 ref -1,=1
def b # b 的 ref -1,=1
事实上,Python 本身能够处理这种情况, 可以显示调用 gc.collect(),来启动垃圾回收。
import os
import psutil
import gc
# 显示当前 python 程序占用的内存大小
def show_memory_info(hint):
pid = os.getpid()
p = psutil.Process(pid)
info = p.memory_full_info()
memory = info.uss / 1024 / 1024
print("{} memory used: {} MB".format(hint, memory))
def func_1():
show_memory_info('initial')
a = [i for i in range(10000000)]
b = [i for i in range(10000000)]
show_memory_info('after a,b created')
a.append(b)
b.append(a)
if __name__ == "__main__":
# func_1()
# show_memory_info('finished')
func_1()
gc.collect()
show_memory_info('finished')
#############输出################
initial memory used: 8.49609375 MB
after a,b created memory used: 783.20703125 MB
finished memory used: 783.20703125 MB
initial memory used: 8.3984375 MB
after a,b created memory used: 783.1796875 MB
finished memory used: 8.73828125 MB
所以,Python 的垃圾回收机制并没有那么弱。
标记-清除
标记清除(Mark-Sweep) 算法是一种基于追踪回收(tracing GC)技术实现的垃圾回收算法。它分为两个阶段:第一个阶段是标记阶段,GC会把所有的活动对象打上标记,第二个阶段是把那些没有标记的对象非活动对象进行回收,那么GC又是如何判断哪些是活动对象哪些是非活动对象呢?
对象时间通过引用(指针)联系在一起,构成一个有向图,对象构成这个有向图的节点,而引用关系构成这个有向图的边。从跟对象(root object)出发,沿着有向边遍历对象,可达的(reachable)对象标记为活动对象,不可达的对象就是要被清除的非活动对象。根对象就是全局变量、调用栈、寄存器。
每次遍历全图,对于 Python 而言是一种巨大的性能浪费。所以,在 Python 的垃圾回收实现中,mark-sweep 使用双向链表维护了一个数据结构,并且只考虑容器类的对象(只有容器类的对象,才有可能才生循环引用)。
缺点:清除非活动的对象前它必须顺序扫描整个堆内存,哪怕只剩下小部分活动对象,也要扫描所有对象。
分代回收
分代回收是一种以空间换时间的操作方式,Python 将内存根据对象的存活时间分为不同的集合,每个集合称为一个代,Python 将内存分为3“代”,分别为年轻代(第0代),中年代(第1代),老年代(第2代),他们对应三个链表,其垃圾回收频率与对象的存活时间的增大而减少。新创建的对象都会被分配到年轻代,年轻代链表的总数达到上限,Python垃圾回收机制就会被触发,把那些可以被回收的对象回收掉,而那些不会被回收的对象就被移到中年代上去,以此类推,老年代中的对象是存活时间最久的对象,甚至是存活于整个系统的生命周期。