python的垃圾回收机制

大家好,很多人说python得益于其自动垃圾回收机制,使得开发人员无需过分关注底层的内存管理,确实,python是一门解释型语言,确定变量类型、分配和释放内存空间都是由python解释器运行的时候进行的,但是如果我们对python的内部垃圾回收机制不了解,当程序发生内存泄漏、代码运行效率优化等情况的时候,大概率会无从下手吧!下面我们就来讲一下python的垃圾回收机制。

关于垃圾回收的算法我们需要了解以下3种算法:

(1)引用计数算法(reference counting)

(2) 标记-清除算法(Mark and Sweep);

(3) 分代回收算法(Generational garbage collector)

python中的垃圾回收机制主要就是以上3种算法,以引用计数为主,标记-清除和分代回收两个算法为辅的结合机制。下面我们就来分别讲以下这三种算法。

引用计数算法:

算法原理是每个对象都有一个用来记录对象被引用次数的计数属性,如果一个对象被引用了,那么引用计数就会 加1,如果引用被删除了的话,引用计数就会 减1,那么当该对象的引用计数为0时,就说明这个对象没有被使用,就会直接回收掉。

我们如何去获取到指定对象的引用的次数呢?

在python中我们使用sys模块包中的getrefcount函数来获取,举个例子看一下:

示例中引用计数为2,因为对象被创建的时候一次。创建后被赋值一次

既然是计数,那么需要知道增减条件,加一 还是 减一。

引用计数加一的情况有:

(1)对象被创建,eg:Work();(2)对象被引用 eg:wname=Work();

(3)对象作为函数的参数的时候 eg: function(wname);

(4)对象是容器中的一个元素时 eg: 作为列表中的元素 olist=[wname, 'xiaoyu']

注意:这里所说的容器,可不是指的docker,而是像列表、元组、集合这些。

引用计数减一的情况有(与加一的情况遥相呼应):

(1)对象被显示销毁 eg: del wname; (2)变量被赋予新的对象 eg:wname='dayu';

(3)对象离开了其作用域 eg: 函数执行完毕;

(4)对象所在的容器被销毁,或者对象从容器中删除。

eg: >>> olist=[wname, 'dayu'] >>>del olist

计数示例代码:

引用计数虽然好用,但是就遇到循环引用的时候,我们就会发现,引用次数永远不会为0,这样就没有办法回收了,由此引出辅助算法 标记-清除算法。

循环引用也就是自己引用自己,或者两个对象之间互相引用,没有第三方对象接入的情况。如下举例:

标记-清除 算法

为了解决容器对象可能产生的循环引用,python中采用了“标记-清除”算法来实现垃圾回收。

算法原理从算法名称中可以看出是分为两个阶段,第一阶段标记,第二阶段清除。

对象之间通过引用(也可成为指针)连在一起,构成一个有向图,有向图的节点就是各个对象,有向图的边就是引用关系。从一个根对象(一般是全局变量、寄存器、调用栈)沿着有向边向下找对象,可以到达的标记为活动对象,不能到达的是非活动对象,而非活动对象就是需要被清除的,可以看一个有向图:

其中黑色实心点就是根对象,我们看到,4、5对象明显是非活跃的,也是被清除的对象。

分代回收算法

我们在执行垃圾回收的过程中,程序会被暂停,为了减少程序的暂停时间,采用分代回收算法降低垃圾回收的耗时,是一种以空间换时间的操作方式。

算法原理是Python把对象的生命周期分为三代,分别是第0代、第1代、第2代。每一代使用双向链表来标记这些对象。每一代链表都有总数阈值,当达到阈值的时候就会出发GC回收,将需要清除的清除掉,不需要清除的移到下一代。以此类推,第2代中的对象存活周期最长的对象。

分代回收的设计思想是,新创建的对象更有可能被回收,存活时间越长的对象更有可能继续存活,这样减少不同的收集频率,节省计算量,提高python性能。

关于各代阈值的获取和设置,如下代码:

gc模块是我们在python中进行内存管理的接口,使用之前引入,import gc ;下面写一下模块中常用的函数及其用途:

gc.set_debug(flags) :设置gc的debug日志,一般设置为gc.DEBUG_LEAK可以看到内存泄漏的对象。

gc.collect([generation]) :执行垃圾回收。会将那些有循环引用的对象给回收了。这个函数可以传递参数,0代表只回收第0代的的垃圾对象、1代表回收第0代和第1代的对象,2代表回收第0、1、2代的对象。如果不传参数,那么会使用2作为默认参数。手动进行垃圾回收的时候就调用该函数

gc.get_threshold() :获取gc模块执行垃圾回收的阈值。返回的是个元组,第0个是零代的阈值,第1个是1代的阈值,第2个是2代的阈值。

gc.set_threshold(threshold0[, threshold1[, threshold2]) :设置执行垃圾回收的阈值。

gc.get_count() :获取当前自动执行垃圾回收的计数器。返回一个元组。第0个是零代的垃圾对象的数量,第1个是零代链表遍历的次数,第2个是1代链表遍历的次数。

结语:python中虽然有自动回收机制,但是我们也需要熟悉了解他们的原理,使我们的代码写的更好,比如:手动执行垃圾回收、避免循环引用 以及适当提高垃圾回收的阈值这些都是可以的。

希望文章对大家有所帮助,欢迎点赞收藏,参与评论,谢谢大家!

你可能感兴趣的:(python的垃圾回收机制)