--------------【正文开始】--------------
前言:
前面把动态类型,垃圾回收都浅显地带过了,今天着重谈一谈python中的垃圾回收机制。江湖上都流传着一“人生苦短我用python!”的口号,很大程度上归功于,python创世者们设计了一个相对完善的垃圾回收机制。所以今天就通过一些参考案例来看看python如何处理垃圾。

每个对象维护一个ob_ref字段,用来记录该对象当前被引用的次数,每当新的引用指向该对象时,它的引用计数ob_ref加1,每当该对象的引用失效时计数ob_ref减1,一旦对象的引用计数为0,该对象立即被回收,对象占用的内存空间将被释放
1.2 标记清除(Mark—Sweep)
一种基于追踪回收(tracing GC)技术实现的垃圾回收算法。它分为两个阶段:第一阶段是标记阶段,GC会把所有的『活动对象』打上标记,第二阶段是把那些没有标记的对象『非活动对象』进行回收。那么GC又是如何判断哪些是活动对象哪些是非活动对象的呢?

对象之间通过引用(指针)连在一起,构成一个有向图,对象构成这个有向图的节点,而引用关系构成这个有向图的边。从根对象(root object)出发,沿着有向边遍历对象,可达的(reachable)对象标记为活动对象,不可达的对象就是要被清除的非活动对象。根对象就是全局变量、调用栈、寄存器。mark-sweepg 在上图中,我们把小黑圈视为全局变量,也就是把它作为root object,从小黑圈出发,对象1可直达,那么它将被标记,对象2、3可间接到达也会被标记,而4和5不可达,那么1、2、3就是活动对象,4和5是非活动对象会被GC回收。
以空间换时间的操作方式,Python 将内存根据对象的存活时间划分为不同的集合,每个集合称为一个代,Python将内存分为了3“代”,分别为年轻代(第0代)、中年代(第1代)、老年代(第2代),他们对应的是3个链表,它们的垃圾收集频率与对象的存活时间的增大而减小。
新创建的对象都会分配在年轻代,年轻代链表的总数达到上限时,Python垃圾收集机制就会被触发,把那些可以被回收的对象回收掉,而那些不会回收的对象就会被移到中年代去,依此类推,老年代中的对象是存活时间最久的对象,甚至是存活于整个系统的生命周期内。同时,分代回收是建立在标记清除技术基础之上。分代回收同样作为Python的辅助垃圾收集技术处理那些容器对象
以引用计数法为例子,在Python内部,实际上,每个对象中都保持了一个计数器,计数器记录了当前指向该对象的引用次数,也就是该对象被引用的次数。一旦这个计数器被设置为零,这个对象的内存空间就会被自动回收。垃圾回收最直接且可感受的好处就是,可以在脚本中任意使用该对象而不需要考虑释放内存空间。与C和C++这样的底层语言相比,省去了大量基础代码。这些是理论的东西,下面开始上实例。
a=5b=aprint(a)print(b) #变量a和 b数值相同
该实例中,第一行创建了对象5,并将变量a与之关联,第二行创建了变量b,变量b也成为对象5的一个引用。实际上,变量a和变量b都引用了相同的对象,都指向了相同的内存空间,这在Python语言中叫作共享引用——多个变量名引用同一对象。简单来说就是对象5只有一个,但存在两个变量a,b都指向这个对象。
a=5b=aa ='five' print(a,b)
第三行代码创建了一个新的对象'"five',并设置a对这个新的对象进行引用,而b仍然继续引用之前的对象5。
与其他语言不同,在 Python中,给一个变量赋一个新的值,并不是替换了原始的对象,而是重新创建一个不同的对象,并让这个变量去引用这个新的对象。实际效果就是,对一个变量赋值,仅仅会影响被赋值的变量。
但是,也有一些特殊的情况,当引用一些可变对象时,在原处对对象进行修改时,就会出现不一样的情况。例如,在一个列表中对某一个偏移位置进行重新赋值时,会改变这个列表对象,而不是生成一个新的对象。首先看一个容易理解的实例:
a=[1,2,31]b=aprint(a)print(b)a=999print(b)
由程序执行结果可以看出,上述实例中一开始变量a和 b都引用了列表对象[1,2,3],后来当对a重新赋值后,创建了新的对象999,并让a引用了这个新的对象,整个过程中b并没有发生变化,这与前面的实例类似,同属于共享引用的范畴。
a=[1,2,31] #创建列表对象[1,2,3]和变量a.并让 a引用该对象b=a #创建变量b、并让b引[用同一列表对象print(a)print(b) #变量a和 b数值相同a[0]='one' #修改变量所引用的对象的一个元素print(a) #变量a数值发生变化print(b) #变量b数值也发生变化
在上述程序中,我们没有改变a,只是改变了a所引用对象的一个元素,这类修改会覆盖列表对象中的某些部分,它不仅仅会影响变量a,也会同时影响变量b,因为它们引用的是同一个列表对象。对于这种在原处修改的对象,共享引用时需要加倍小心,不注意的话非常容易出错。
如果不希望上述情况出现时,需要使用Python的对象复制,而不是创建引用。Python有多种复制列表的方法,现列举如下。
a=[1,2,3]b=a[:] #复制列表print(b)print(a[0])print(a) #a引用的列表中某一元素变化时,b未改变。
这种情况下,对a的修改不会影响b,因为b引用的是a所引用对象的复制,两个变量指向了不同的内存区域。需要注意的是,这种分片技术不能用于集合和字典等非序列类型的对象中。
除了上述复制方法之外,还可以使用copy()函数实现,例如:
import copya=11,2.31b=copy.copy(a)print(b)print(a[0])print(a)
这些例子都很短小,但是也相当的有代表性,我也是反复的回味这些知识,每每回味都觉得很妙。
古人言,故不积跬步,无以至千里;不积小流,无以成江海。学习一门技术也是这样,只有持之以恒,才能不断精进。