Python的垃圾回收机制(引用计数+标记清除+分代回收)

1、写在前面:

咱们都知道Python一种面向对象的脚本语言,对象是Python中很是重要的一个概念。在Python中数字是对象,字符串是对象,任何事物都是对象,而它们的核心就是一个结构体--PyObject。算法

typedef struct_object{编程

  int ob_refcnt;app

  struct_typeobject *ob_type;编程语言

}PyObject;函数

PyObject是每一个对象必有的内容,其中ob_refcnt就是作为引用计数。spa

 

2、垃圾回收机制

垃圾回收(Garbage Collection)你们应该多多少少都听过,可是什么是垃圾回收呢?咱们这里说的垃圾回收确定不是把垃圾丢进垃圾桶。如今的高级语言Java,C#等,都采用了垃圾回收机制,而再也不是C,C++里用户本身管理维护内存的方式,本身管理内存是很自由,可是可能出现内存泄漏,悬空指针等问题。而垃圾回收机制做为现代编程语言的自动内存管理机制,专一于两件事:1. 找到内存中无用的垃圾资源 2. 清除这些垃圾并把内存让出来给其余对象使用。指针

 

3、Python中的垃圾回收

Python中,垃圾回收机制主要是以引用计数为主要手段,以标记清除和分代回收机制做为辅助手段实现的。对象

一、引用计数

经过前面的介绍,咱们已经知道PyObject是每一个对象必有的内容,而当一个对象有新的引用时,它的ob_refcnt就会增长,当引用它的对象被删除,它的ob_refcnt就会减小,当引用计数为0时,该对象生命就结束了。blog

咱们来看看引用计数+1的状况有什么:生命周期

(1)对象被建立:

这里实际上123这个对象并无在内存中新建,由于在Python启动解释器的时候会建立一个小整数池,在-5~256之间的整数对象会被自动加载到内存中等待调用。所以a=123是对123这个整数对象增长了一次引用。而456是不在整数池里的,须要建立对象,那么最后的引用次数是2呢?由于sys.getrefcount(b)也是一次引用。

(2)对象被引用:

 

每一次赋值操做都会增长数据的引用次数,要记住引用的变量a、b、c指向的是数据456,而不是变量自己。

(3)对象做为参数传递到函数中:

 

这里能够很明显看到在被传递到函数中后,引用计数增长了1。

(4)对象做为元素储存到容器中:

 

这里咱们在建立对象以后,把a分别添加到了一个列表和一个元组中,引用计数都增长了。

虽然引用计数必须在每次分配合释放内存的时候加入管理引用计数的操做,然而与其余垃圾回收技术相比,引用计数有一个最大的优势,那就是“实时性”,若是这个对象没有引用,内存就直接释放了,而其余垃圾回收技术必须在某种特殊条件下才能进行无效内存的回收。可是引用计数带来的维护引用计数的额外操做和Python中进行的内存分配和释放,引用的赋值次数成正比的。除此以外,引用计数机制的还有一个最大的软肋--没法解决循环引用带来的问题。循环引用可使一种引用对象的引用计数不为0,然而这些对象实际上并无被任何外部对象所引用,它们之间只是相互引用,这意味着这组对象所占用的内存空间是应该被回收的,可是因为循环引用致使的引用计数不为0,因此这组对象所占用的内存空间永远不会被释放。以下,list1与list2相互引用,若是不存在其余对象对它们的引用,list1与list2的引用计数也仍然为1,所占用的内存永远没法被回收,这将是致命的

 

list1 = []
list2 = []
list1.append(list2)
list2.append(list1)

二、标记清除

标记清除(Mark—Sweep)算法是一种基于追踪回收(tracing GC)技术实现的垃圾回收算法。它分为两个阶段:第一阶段是标记阶段,GC会把全部的活动对象打上标记,第二阶段是把那些没有标记的对象非活动对象进行回收。

对象之间经过引用(指针)连在一块儿,构成一个有向图,对象构成这个有向图的节点,而引用关系构成这个有向图的边。从根对象(root object)出发,沿着有向边遍历对象,可达的(reachable)对象标记为活动对象,不可达的对象就是要被清除的非活动对象。根对象就是全局变量、调用栈、寄存器。

           

在上图中,能够从程序变量直接访问块1,而且能够间接访问块2和3。程序没法访问块4和5。第一步将标记块1,并记住块2和3以供稍后处理。第二步将标记块2,第三步将标记块3,但不记得块2,由于它已被标记。扫描阶段将忽略块1,2和3,由于它们已被标记,但会回收块4和5。

标记清除算法做为Python的辅助垃圾收集技术,主要处理的是一些容器对象,好比list、dict、tuple等,由于对于字符串、数值对象是不可能形成循环引用问题。Python使用一个双向链表将这些容器对象组织起来。不过,这种简单粗暴的标记清除算法也有明显的缺点:清除非活动的对象前它必须顺序扫描整个堆内存,哪怕只剩下小部分活动对象也要扫描全部对象。

三、分代回收

分代回收是创建在标记清除技术基础之上的,是一种以空间换时间的操做方式。

Python将内存根据对象的存活时间划分为不一样的集合,每一个集合称为一个代,Python将内存分为了3“代”,分别为年轻代(第0代)、中年代(第1代)、老年代(第2代),他们对应的是3个链表,它们的垃圾收集频率与对象的存活时间的增大而减少。新建立的对象都会分配在年轻代,年轻代链表的总数达到上限时,Python垃圾收集机制就会被触发,把那些能够被回收的对象回收掉,而那些不会回收的对象就会被移到中年代去,依此类推,老年代中的对象是存活时间最久的对象,甚至是存活于整个系统的生命周期内。

相关文章
相关标签/搜索