c++调用python释放内存_Python中的垃圾回收机制

垃圾回收(Garbage Collection,以下简称GC)是一种自动的内存管理机制,有许多不同的实现算法,Python中的GC,以引用计数为主,标记-清除和分代回收为辅。


1、GC

在程序中定义了一个变量,就是在内存中开辟了一段相应的空间来存值。由于内存是有限的,所以当程序不再需要使用某个变量的时候,就需要销毁该对象并释放其所占用的内存资源,好重新利用这段空间。在C/C++中,无用变量的内存空间,需要由程序员手动释放,这显然非常繁琐,而且一旦有所疏忽,就可能造成内存泄漏(Memory Leak)。当软件系统比较复杂、变量多的时候,程序员往往就忘记释放内存或者在不该释放的时候释放了内存。

有了GC,程序员就不需要再手动地去控制内存的释放,这一切可以交由语言本身来自动完成。GC本质上做了三件事情:1).为新生对象分配内存;2).垃圾检测;3).垃圾回收。GC是一门古老的艺术,大概在1960年就有了,发展至今,已经有相当多的算法,如标记-清除、引用计数、标记-压缩、分代回收等。GC的来源可能是由编程语言本身内置(如Java、C#)或是经由外面的库所提供,而非建制于语言内部,例如贝姆垃圾收集器就是一种可支持C/C++语言的自动内存管理工具。

GC最早起源于LISP语言,目前许多语言如Java、C#、Ruby、Python等都支持GC(值得注意的是,C++自身并不支持GC,读者可参考[1]和[2])。接下来我们就详细地讲解一下Python中的GC。

2、Python中的GC

Python中的GC,以引用计数为主,标记-清除和分代回收为辅。

2.1、引用计数(reference counting)

引用计数,是George E. Collins在1960年发明的,算是最早期的垃圾回收实现方法。

在Python中,每一个对象的核心就是一个结构体PyObject,它的内部有一个引用计数器ob_refcnt

typedef 

当一个对象有新的引用时,对象的引用计数+1;当一个对象的引用被销毁时,对象的引用计数-1;当对象的引用计数减少为0时,就意味着对象已经没有被任何人使用了,可以将其所占用的内存释放。

导致引用计数+1的情况:1).对象被创建;2).对象被复制;3).对象作为函数参数被传入(引用计数+2);4).对象作为一个元素被存储在容器中.

导致引用计数-1的情况:1).对象的别名被显式销毁;2).对象的别名被赋予新的对象;3).一个对象离开它的作用域,例如函数执行完毕后,其中的局部变量;4).对象所在的容器被销毁,或从容器中删除对象。

引用计数机制的优缺点是显而易见的:

优点:
1. 简单;
2. 实时性:一旦引用计数为0,立即被回收;内存回收的时间分摊到平时;
缺点:
1. 需要额外的空间来维护引用计数;
2. 执行效率低:引用计数机制所带来的维护引用计数的额外操作,与程序运行过程中所进行的内存分配、释放和引用赋值的次数成正比

除了上面提到的,引用计数机制还有一个致命缺点,即无法解决循环引用的问题。我们用一段代码来做进一步的解释:

a 

上面的代码中,对象[1, 2]和[3, 4]已经没有了来自外界的引用,这意味着不会再有人使用它们(无法通过其它变量来引用这两个对象),但是它们彼此之间依然有相互的引用,因此引用计数均为1,也就导致它们的内存永远不能被回收。

这一点是致命的,它与手动进行内存管理所产生的内存泄漏无异(因此,也有很多语言比如Java并没有采用引用计数来实现GC)。为了弥补引用计数的缺陷,Python中引入了其它的GC机制。

2.2、标记-清除(mark and sweep)

可以包含其它对象引用的容器对象,如list、set、dict、class、instance,都可能产生循环引用,标记-清除可以解决这个问题。

标记-清除是一种基于追踪(Tracing)回收技术实现的垃圾回收算法。它分为两个阶段:第一阶段是标记,把所有的『活动对象』打上标记,第二阶段是回收,对那些没有标记的『非活动对象』进行回收。那么,如何区分活动对象和非活动对象呢?

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

a5294ce4bf73bb82f75ddfdd55d258ab.png

在上图中,我们把小黑圈视为root object,从小黑圈出发,对象1可达,那么它将被标记,对象2、3可间接可达也会被标记,而4和5不可达,那么1、2、3就是活动对象,4和5是非活动对象会被GC回收。

标记-清除的过程实际比上面说的还要复杂一下,具体来讲,首先找到root object集合,然后在内存中建立两条链表,一条链表中维护root object集合,称为root链表,而另外一条链表中维护剩下的对象,称为unreachable链表。在标记的过程中,如果发现unreachable链表中存在被root链表中的对象,直接或间接引用的对象,就将其从unreachable链表中移到root链表中;当完成标记后,unreachable链表中剩下的所有对象就是名副其实的垃圾对象了,接下来的垃圾回收只需限制在unreachable链表中即可。

2.3、分代回收(generation collection)

分代回收,是一种以空间换时间的回收方式,可以提升GC的效率。

分代回收思想将对象分在不同的集合中,每个集合称为一个“代”(generation),Python中分为3代,分别为年轻代(第0代)、中年代(第1代)、老年代(第2代),他们对应的是3个链表。每一代的GC频率是不同的,第0代最高,第1代次之,第2代最低。

根据弱代假说(即越年轻的对象越容易死掉,而老的对象通常会存活更久),新生的对象被放入第0代,如果该对象在第0代的一次GC中活了下来,那么它就被移动到第1代,类似地,如果某第1代对象在第1代的一次GC中活了下来,它就被移动到第2代。

那么,什么情况下会触发GC呢?具体地,在Python中,gc.set_threshold(threshold0[,threshold1[,threshold2]])可以设置每一代GC被触发的阈值:从上一次第0代GC后,如果分配对象的个数减去释放对象的个数大于threshold0,那么就会对第0代中的对象进行GC; 从上一次第1代GC后,如果第0代被GC的次数大于threshold1,那么就会对第1代中的对象进行GC;同样,从上一次第2代GC后,如果第1代被GC的次数大于threshold2,那么就会对第2代中的对象进行GC。除此之外,还有两种情况会触发GC,第一种是手动调用gc.collect(),第二种便是程序退出。

从上面的叙述可以看出,老年代中的对象是存活时间最久的对象,甚至是存活于整个系统的生命周期内。

2.4、其它

a).

Python中的gc模块提供了一些接口给开发者设置GC相关的选项,具体使用可参考1和2。

b).

如果循环引用中,两个对象都定义了__del__方法,gc模块不会销毁这两个不可达对象,因为gc模块不知道应该先调用哪个对象的__del__方法(例如,两个对象a和b,如果先销毁a,则在销毁b时,会调用b的__del__方法,该方法中很可能使用了a,这时会造成异常),所以为了安全起见,gc模块会把对象放到gc.garbage中,并把它们称为uncollectable。很明显,这种情况会造成内存泄漏,要解决的话,只能显式调用其中某个对象的__del__方法来打破僵局。

c).

还有一种情况会造成Python中的内存泄漏,即对象一直被全局变量所引用,而我们知道,全局变量的生命周期是非常长的。

2.5、小结

写到这里,我们尝试着来小结一下Python中的GC机制:Python中,对于所有对象,引用计数都在起作用,一旦某对象的引用计数为0,它所占用的内存就会被释放;而对于容器对象,由于它们会产生循环引用,这是引用计数所无法解决的,于是Python引入了标记-清除的方式来对它们做GC;最后,为了提升标记-清除的GC效率,Python引入了分代回收的机制,以空间换时间。


参考文献

[1] Python垃圾回收(GC)三层心法,你了解到第几层? - 掘金

[2] Python垃圾回收机制--完美讲解! - 圆旭 - 博客园

[3] Python的垃圾回收机制深入分析--循环引用_python,垃圾回收,循环引用_宇宙浪子的专栏-CSDN博客

[4] Python垃圾回收机制详解 - Xjng - 博客园

[5] Python 垃圾回收机制 - Python之禅 - 开发者头条

[6] Jason:python垃圾回收-Python面试题 171117

[7] Python之美[从菜鸟到高手]--Python垃圾回收机制及gc模块详解_Python_Skycrab-CSDN博客

[8] http://python.jobbole.com/83548/?utm_source=blog.jobbole.com&utm_medium=relatedPosts

[9] http://python.jobbole.com/87843/?utm_source=blog.jobbole.com&utm_medium=relatedPosts

[10] Memory Management Reference

[11] https://zh.wikipedia.org/wiki/%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6_(%E8%A8%88%E7%AE%97%E6%A9%9F%E7%A7%91%E5%AD%B8)

[12] http://blog.jobbole.com/109833/

[13] GC(垃圾回收)_百度百科

[14] 内存泄漏_百度百科

[15] 为什么程序员要学GC

[16] C++为什么不加入垃圾回收机制_C/C++_M-先生-CSDN博客

[17] c++没有垃圾回收机制的原因_C/C++_点滴的岁月-CSDN博客

[18] 为什么Python要使用有明显缺陷的引用计数而不是像JavaScript一样的标记清除?

以上为本文的全部参考文献,对原作者表示感谢。


我的足迹

  • CSDN
  • GitHub
表情包
插入表情
评论将由博主筛选后显示,对所有人可见 | 还能输入1000个字符
相关推荐
©️2020 CSDN 皮肤主题: 数字20 设计师:CSDN官方博客 返回首页