Python GC主要使用引用计数
来跟踪和回收垃圾。在引用计数的基础上,通过标记-清除
解决容器对象可能产生的循环引用问题,通过分代回收
以空间换时间的方法提高垃圾回收效率。
Python采用的是引用计数机制为主,标记-清除和分代收集两种机制为辅的策略。
引用计数
PyObject是每个对象必有的内容,其中ob_refcnt
就是引用计数。当一个对象有新的引用时,它的ob_refcnt
就会增加,当引用它的对象被删除,它的ob_refcnt
就会减少。引用计数为0时,该对象生命就结束了。
通过使用内置的模块sys.getrefcount()
可以查看对象的引用计数,但是会比正常计数大1。因为调用函数的时候需要传入对象使得其引用计数增加1。
-
增加引用计数:对象被创建、对象被引用、对象被作为参数传入到一个函数中、对象作为一个元素存储在容器中
-
减少引用计数:对象的别名被显示销毁、对象的别名被赋予新的对象、一个对象离开了它的作用域、对象所在的容器被销毁后从容器中删除对象
一个误区:Python系统维护着一个常见的整数常量池
,即-5~255
,在这个区间的数字会有其他的处理方式。包括字符串也有一些特殊的处理,所以在使用应用技术的时候,最好是使用自己自定义的数据类型,这样方便分析。
import sys
nu = 10
print(sys.getrefcount(nu)-1) # 16
import sys
class A:
pass
a = A()
print(sys.getrefcount(a)-1) # 1
b = a
print(sys.getrefcount(a)-1, sys.getrefcount(b)-1) # 2 2
del b
print(sys.getrefcount(a)-1) # 1
引用计数的致命缺陷:循环引用
导致的内存泄漏
内存泄漏
是指由于疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误失去了对该段内存的控制,因而造成了内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。
导致内存泄漏的主凶是有__del__()函数
的对象之间的循环引用。在不使用一个对象的时候,应使用del object
来删除一个对象的引用计数,这样就可以有效地防止内存泄漏的问题。
通过Python扩展模块gc
来查看不能回收的对象的详细信息。
import sys
lstOne = []
lstTwo = []
lstOne.append(lstTwo)
lstTwo.append(lstOne)
print(sys.getrefcount(lstOne), sys.getrefcount(lstTwo)) # 3 3
lstOne.remove(lstTwo)
print(sys.getrefcount(lstOne), sys.getrefcount(lstTwo)) # 3 2
标记-清除机制
基本思路:先按需分配,等到没有空闲内存的时候从寄存器和程序栈上的引用出发,遍历以对象为节点、以引用为边构成的图,把所有可以访问到的对象打上标记,然后清扫一遍内存空间,把所有没标记的对象释放。
标记清除算法
是一种基于追踪回收技术实现的垃圾回收算法。它分为两个阶段。
-
标记阶段:GC会把所有的
活动对象
打上标记 -
回收阶段:把那些没有标记的对象
非活动对象
进行回收
对象之间通过引用(指针)连在一起,构成一个有向图,其中对象构成这个有向图的节点,而引用关系构成这个有向图的边。从根对象出发,沿着有向边遍历对象,可达的对象标记为活动对象,不可达的对象就是要被清除的非活动对象。根对象就是全局变量、调用栈、寄存器。
作为Python的辅助垃圾收集技术,标记清除算法主要处理的是一些容器对象,比如list、dict、tuple,instance等,因为对于字符串、数值对象是不可能造成循环引用问题。Python使用一个双向链表将这些容器对象组织起来。
缺点:清除非活动的对象前必须顺序扫描整个堆内存,哪怕只剩下小部分活动对象也要扫描所有对象。
分代技术
整体思想:将系统中的所有内存块根据其存活时间划分为不同的集合,每个集合就成为一个代
,垃圾收集的频率随着代
的存活时间增大而减小,存活时间通常利用经过几次垃圾回收来度量。
Python默认定义了三代对象集合,索引数越大,对象存活时间越长。
分代技术
是一种典型的以空间换时间的技术,简单点说就是:对象存在时间越长,越可能不是垃圾,应该越少去收集。
分代就是将回收对象分成数个代
,每个代
就是一个链表(集合),代
进行标记-清除的时间与代
内对象
存活时间成正比例关系。
Python里一共有三代
,每个代
的threshold值表示该代
最多容纳对象的个数。默认情况下,当0代
超过700,或1、2代
超过10,垃圾回收机制将触发。0代
触发将清理所有三代
,1代
触发会清理1、2代
,2代
触发后只会清理自己。
分代回收
是建立在标记-清除技术
基础之上,分代回收同样作为Python的辅助垃圾收集技术处理那些容器对象。
垃圾回收与性能调优
手动垃圾回收、调高垃圾回收阈值、避免循环引用(手动解循环引用和使用弱引用)
(最近更新:2019年04月11日)