在Java中,对象所占用的内存在对象再也不使用后会自动被回收。这些工做是由一个叫垃圾回收器 (Garbage Collector
)的进程完成的。python
python和其余不少高级语言同样,都自带垃圾回收机制,即GC机制。算法
Python中的垃圾回收是以引用计数为主,标记-清除和分代收集为辅。引用计数最大缺陷就是循环引用的问题,因此Python采用了辅助方法。缓存
注意:app
一、垃圾回收时,Python不能进行其它的任务,频繁的垃圾回收将大大下降Python的工做效率;函数
二、Python只会在特定条件下,自动启动垃圾回收(垃圾对象少就不必回收)spa
三、当Python运行时,会记录其中分配对象(object allocation)和取消分配对象(object deallocation)的次数。当二者的差值高于某个阈值时,垃圾回收才会启动。操作系统
Python内部使用引用计数,来保持追踪内存中的对象,全部对象都有引用计数。指针
原理code
引用计数法的原理是每一个对象维护一个ob_refcnt,用来记录当前对象被引用的次数,也就是来追踪到底有多少引用指向了这个对象。当一个对象有新的引用时,它的ob_refcnt就会增长,当引用它的对象被删除,它的ob_refcnt就会减小。当引用计数为0时,该对象生命就结束了。对象
源码以下:
// object.h struct _object { Py_ssize_t ob_refcnt; # 引用计数值 struct PyTypeObject *ob_type; } PyObject;
引用计数增长的状况:
引用计数减小状况
引用计数法有很明显的优势:
原始的引用计数法也有明显的缺点:
# 循环引用的示例: list1 = [] list2 = [] list1.append(list2) list2.append(list1)
对于现现在强大的硬件来讲,缺点维护引用计数消耗资源还尚可接受,可是循环引用致使内存泄漏却无疑是致命的,所以python还引入了新的回收机制(标记清除和分代收集)
针对循环引用的状况:咱们有一个“孤岛”或是一组未使用的、互相指向的对象,可是谁都没有外部引用。换句话说,咱们的程序再也不使用这些节点对象了,因此咱们但愿Python的垃圾回收机制可以足够智能去释放这些对象并回收它们占用的内存空间。可是这不可能,由于全部的引用计数都是1而不是0。Python的引用计数算法不可以处理互相指向本身的对象。你的代码也许会在不经意间包含循环引用而且你并未意识到。事实上,当你的Python程序运行的时候它将会创建必定数量的“浮点数垃圾”,Python的GC不可以处理未使用的对象由于应用计数值不会到零。
这就是为何Python要引入Generational GC算法的缘由!
『标记清除(Mark—Sweep)』算法是一种基于追踪回收(tracing GC)技术实现的垃圾回收算法。它分为两个阶段:第一阶段是标记阶段,GC会把全部的『活动对象』打上标记,第二阶段是把那些没有标记的对象『非活动对象』进行回收。那么GC又是如何判断哪些是活动对象哪些是非活动对象的呢?
对象之间经过引用(指针)连在一块儿,构成一个有向图,对象构成这个有向图的节点,而引用关系构成这个有向图的边。从根对象(root object)出发,沿着有向边遍历对象,可达的(reachable)对象标记为活动对象,不可达的对象就是要被清除的非活动对象。根对象就是全局变量、调用栈、寄存器。
标记清除算法做为Python的辅助垃圾收集技术主要处理的是一些容器对象,好比list、dict、tuple,instance等,由于对于字符串、数值对象是不可能形成循环引用问题。Python使用一个双向链表将这些容器对象组织起来。不过,这种简单粗暴的标记清除算法也有明显的缺点:清除非活动的对象前它必须顺序扫描整个堆内存,哪怕只剩下小部分活动对象也要扫描全部对象。
标记清除算法在申请内存时,全部容器对象的头部又加上了PyGC_Head来实现“标记-清除”机制。任何一个python对象都分为两部分: PyObject_HEAD + 对象自己数据
源码以下:
// objimpl.h typedef union _gc_head { struct { union _gc_head *gc_next; union _gc_head *gc_prev; Py_ssize_t gc_refs; } gc; long double dummy; /* force worst-case alignment */ } PyGC_Head;
python在建立对象以前,会建立一个链表,零代链表,只不过这个链表是空的。每当你建立一个对象,python便会将其加入到零代链表。
python隔代回收的核心:对链子上的那些明明没有被引用但引用计数却不是零的对象进行引用计数减去一,看看你是否是垃圾。若是被引用屡次减去一以后仍不为零,那么会在零代链表当中继续被清理,直至引用计数为零。由于若是没有变量指向它,或者做为函数的参数,列表的元素等等,那么它就始终是零代链表中被清理的对象。当零代链表被清理达到必定次数时,会清理一代链表。一代链表被清理达到必定次数时,会清理二代链表。
所以清理的频率最高的是零代链表,其次是一代链表,再是二代链表。
原理:
内存池(金字塔):