您的位置:首页 > 编程语言 > Python开发

Python——垃圾回收机制

2019-06-10 13:09 232 查看

1.对象池

在了解垃圾回收机制之前,需要先了解对象池是什么?

对象池中包含若干提前准备好的若干实例,当需要时从对象池中提取,当不需要时,则重新放入对象池。

一方面,使用对象池不需要频繁的产生和销毁实例对象,另一方面,对象池中的实例如果不够程序调用才会继续产生实例,这大大节省了性能。

1)整数对象池

整数对象池又分为小整数对象池和大整数对象池。

小整数池:整数在程序中的使用非常广泛,Python为了优化速度,使用了小整数对象池,避免为整数频繁申请和销毁内存空间。

Python对小整数的定义是[-5,257),这些整数对象是提前建立好的,不会被垃圾回收。在一个Python的程序中,所有位于这 个范围内的整数使用的都是同一个对象。

大整数池:而大整数就是在小整数的定义范围以外的整数,每一个大整数,均需要创建一个新的对象。 

2)字符串常量池

有一个字符串驻留机制,它通过维护一个字符串常量池(string intern pool),从而试图只保存唯一的字符串对象,达到既高效又节省内存地处理字符串的目的。

在创建一个新的字符串对象后,Python 先比较常量池中是否有相同的对象(interned), 有的话则将指针指向已有对象,并减少新对象的指针,新对象由于没有引用计数,就会被垃圾回 收机制回收掉,释放出内存。

注意:当字符串含有空格时,不可修改,没开启intern机制,这时不共用对象,引用计数为0,则销毁。

 

2.垃圾回收机制

 内存溢出 out of memory:是指程序在申请内存时,没有足够的内存空间供其使用, 出现out of memory;比方说,定义了20个字节大小的内存空间,却写入了21个字 节的数据。通俗的说,就是内存不够,没办法支持当前程序。当发生内存溢出时,程序将无法进行,强制终止。

内存泄露 memory leak:是指程序在申请内存后,无法释放已申请的内存空间,一次内存 泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光。如果发生内存泄露,那么可用内存会逐渐减少,从而降低性能。

内存泄露最终会导致内存溢出。

python采用的是引用计数机制为主,标记-清除和分代收集两种机制为辅的策略。

1) 引用计数机制

垃圾回收(GC) 原本是一种“释放怎么都无法被引用的对象的机制”。那么人们自然而然地就会想到,可以让所有对象事先记录下“有多少程序引用自己”。让各对象知道自己的“人气指 数”,从而让没有人气的对象自己消失,这就是引用计数法(Reference Counting)。

python里每一个东⻄都是对象,它们的核心就是结构体: PyObject    ,如下图:

那么如果当引用计数为0时,该对象生命就结束了。

那什么情况下引用计数会增加,什么情况下引用计数会减少呢?

导致引用计数+1的情况:

  1. 对象被创建,例如a=23         
  2. 对象被引用,例如b=a         
  3. 对象被作为参数,传入到一个函数中,,例如func(a)         
  4. 对象作为一个元素,存储在容器中,例如list1=[a,a]

导致引用计数-1的情况:

  1. 对象的别名被显式销毁,例如del  a         
  2. 对象的别名被赋予新的对象,例如a=24         
  3. 一个对象离开它的作用域,例如f函数执行完毕时,func函数中的局部变量(全局变量不会)         
  4. 对象所在的容器被销毁,或从容器中删除对象     

引用计数的优点:

  1. 简单
  2. 实时性:一旦没有引用,内存就直接释放了。不用像其他机制等到特定 时机。实时性还带来一个好处:处理回收内存的时间分摊到了平时。 

 引用计数的缺点:

  1. 维护引用计数消耗资源
  2. 循环引用

下面看一个循环引用的例子:

[code]import time
import gc
# 关闭python自动实现的垃圾回收机制;
gc.disable( )

while True:
time.sleep(0.0003)
# 申请两个内存空间, 变量list1和变量list2分别指向不同的内存空间, 引用计数全为1;
list1 = [ ]
list2 = [ ]

# 在容器list1里面存储了list2, list2的引用计数加1;
list1.append(list2)
# 在容器list2里面存储了list1, list1的引用计数加1;
list2.append(list1)

# 删除list1和list2
del list1
del list2

list1与list2相互引用,如果不存在其他对象对它们的引用,list1与list2的引用 计数也仍然为1,所占用的内存永远无法被回收,这将是致命的。    对于如今的强大硬件,缺点1尚可接受,但是循环引用导致内存泄露,注定python还将引入新的回收机制,那就是标记清除和分代收集。

 

2)标记清除

和它的字面意思一样,GC 标记 - 清除算法由标记阶段和清除阶段构成。标记阶段是 把所有活动对象都做上标记的阶段。清除阶段是把那些没有标记的对象,也就是非活动对象回收的阶段。通过这两个阶段,就可以令不能利用的内存空间重新得到利用。

如果说被标记的对象是存活的,剩下的未被标记的对象只能是垃圾,这意味着我们的代码不再会使用它了,如下图所示:

清除这些无用的垃圾对象,把它们送回到可用列表。

 

3)分代收集

分代垃圾回收(Generational GC)在对象中导入了“年龄”的概念,通过优先回收容 易成为垃圾的对象,提高垃圾回收的效率。

新生代对象和老年代对象具体是什么:

  1. 分代垃圾回收中把对象分类成几代,针对不同的代使用不同的 GC 算法,我们把刚生成 的对象称为新生代对象,到达一定年龄的对象则称为老年代对象。
  2. 新生代 GC 将存活了一定次数的新生代对象当作老年代对象来处理。我们把类似于这 样的新生代对象上升为老年代对象的情况称为晋升(promotion)。
  3. 老年代对象很难成为垃圾,所以我们对老年代对象减少执行 GC 的频率,从而提高效率。  

人们从众多程序案例中总结出了一个经验:“大部分的对象在生成后马上就变成了垃圾,很少有对象能活得很久。”分代垃圾回收利用该经验,在对象中导入了“年龄”的概念,经历 过一次 GC 后活下来的对象年龄为 1 岁。 机制如下图所示:

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: