• 主页
  • 相册
  • 随笔
  • 目录
  • 存档
Total 244
Search AboutMe

  • 主页
  • 相册
  • 随笔
  • 目录
  • 存档

python垃圾回收

2020-01-17

1. 概述

Python’s memory allocation and deallocation method is automatic. The user does not have to preallocate or deallocate memory similar to using dynamic memory allocation in languages such as C or C++.

2. 具体实现

Python uses two strategies for memory allocation:

  • Reference counting
  • Garbage collection

Python中的垃圾回收是以引用计数为主,分代收集为辅

3. Reference counting(引用计数)


Reference counting works by counting the number of times an object is referenced by other objects in the system. When references to an object are removed, the reference count for an object is decremented. When the reference count becomes zero, the object is deallocated.

1
2
3
4
5
6
# Literal 9 is an object 
b = 9

# Reference count of object 9
# becomes 0.
b = 4
**优点**
  • 最大暂停时间短

    因为只要程序更新指针时程序就会执行垃圾回收,也就是每次通过执行程序生成垃圾时,这些垃圾都会被回收,内存管理的开销分布于整个应用程序运行期间,无需挂起应用程序的运行来做,因此消减了最大暂停时间

  • 不需要沿指针查找

缺点

  • Python不得不在每个对象内部留一些空间来处理引用数。这样付出了一小点儿空间上的代价。但更糟糕的是,每个简单的操作(像修改变量或引用)都会变成一个更复杂的操作,因为Python需要增加一个计数,减少另一个,还可能释放对象
  • 它相对较慢。Python不停地更新着众多引用数值。特别是当你不再使用一个大数据结构的时候,比如一个包含很多元素的列表,Python可能必须一次性释放大量对象。减少引用数就成了一项复杂的递归过程了
  • 引用计数不能处理环形数据结构–也就是含有循环引用的数据结构

4. reference cycle(循环引用)

A reference cycle is created when there is no way the reference count of the object can reach. Reference cycles involving lists, tuples, instances, classes, dictionaries, and functions are common

1
2
3
4
5
6
>list1 = []
list2 = []
list1.append(list2)
list2.append(list1)
del(list1) #list1=None
del(list2) #list2=None

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

The reference count for the list created is now two. However, since it cannot be reached from inside Python and cannot possibly be used again, it is considered garbage. In the current version of Python, this list is never freed.

  • 为了解决对象的循环引用问题,Python 引入了标记清除和分代回收两种GC机制

5. 内存溢出

  • 存在循环引用,gc不能释放;
  • 存在全局对象,该对象不断的变大,占据内存;
  • 使用了c或者c++扩展,扩展内存溢出了;

6. 内存泄露

  • 循环引用的变量并不会被回收,它会一直驻留在内存中,就会造成了内存泄漏(内存空间在使用完毕后未释放)

7. Mark and Sweep(标记清除)

标记清除算法作为 Python 的辅助垃圾收集技术主要处理的是容器对象(container,上面讲迭代器有提到概念),比如list、dict、tuple等,因为对于字符串、数值对象是不可能造成循环引用问题。Python使用一个双向链表将这些容器对象组织起来

* 如果说被标记的对象是存活的,剩下的未被标记的对象只能是垃圾,这意味着我们的代码不再会使用它了 * **缺点** * 清除非活动的对象前它必须顺序扫描整个堆内存,哪怕只剩下小部分活动对象也要扫描所有对象

8. generational GC(分代回收)

零代(Generation Zero)

Python使用一种链表来持续追踪活跃的对象。Python的内部C代码将其称为零代(Generation Zero)。每次当你创建一个对象或其他什么值的时候,Python会将其加入零代链表

原理

​Python将内存根据对象的存活时间划分为不同的集合,每个集合称为一个代,Python将内存分为了3“代”,分别为年轻代(第0代)、中年代(第1代)、老年代(第2代),他们对应的是3个链表,它们的垃圾收集频率与对象的存活时间的增大而减小。

新创建的对象都会分配在年轻代,年轻代链表的总数达到上限时,Python垃圾收集机制就会被触发,把那些可以被回收的对象回收掉,而那些不会回收的对象就会被移到中年代去,依此类推,老年代中的对象是存活时间最久的对象,甚至是存活于整个系统的生命周期内。

​ 分代回收是建立在标记清除技术基础之上


The GC classifies objects into three generations depending on how many collection sweeps they have survived. New objects are placed in the youngest generation (generation 0). If an object survives a collection it is moved into the next older generation. Since generation 2 is the oldest generation, objects in that generation remain there after a collection. In order to decide when to run, the collector keeps track of the number object allocations and deallocations since the last collection. When the number of allocations minus the number of deallocations exceeds threshold0, collection starts. Initially only generation 0 is examined. If generation 0 has been examined more than threshold1 times since generation 1 has been examined, then generation 1 is examined as well. Similarly, threshold2 controls the number of collections of generation 1 before collecting generation 2.

9. 检测循环引用

Python会循环遍历零代列表上的每个对象,检查列表中每个互相引用的对象,根据规则减掉其引用计数。在这个过程中,Python会一个接一个的统计内部引用的数量以防过早地释放对象

* 蓝色的箭头指示了有一些对象正在被零代链表之外的其他链上的对象所引用 * "ABC"与"DEF"相互引用

处理

* ABC和DEF的引用计数已经变为零了,这意味着收集器可以**释放它们并回收内存空间**了。剩下的活跃的对象则被移动到一个新的链表:一代链表

threshold——何时调用分代回收

When the number of allocations minus the number of deallocations is greater than the threshold number, the garbage collector is run.

One can inspect the threshold for new objects (objects in Python known as generation 0 objects) by importing the gc module and asking for garbage collection thresholds

1
2
3
4
5
6
7
# loading gc 
import gc

# get the current collection
# thresholds as a tuple
print("Garbage collection thresholds:",
gc.get_threshold())

Output:Garbage collection thresholds: (700, 10, 10)

  • Here, the default threshold on the above system is 700. This means when the number of allocations vs. the number of deallocations is greater than 700 the automatic garbage collector will run

10. 具体实现

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

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

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

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

触发垃圾回收

  • 调用gc.collect()显式进行垃圾回收
  • gc模块的计数器达到阀值
  • 程序退出

11. 内存泄露

在计算机科学中,内存泄漏指由于疏忽或错误造成程序未能释放已经不再使用的内存。内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,导致在释放该段内存之前就失去了对该段内存的控制,从而造成了内存的浪费。

  • 维基百科编者. 内存泄漏[G/OL]. 维基百科, 2019(20191126)[2019-11-26].

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class ClassA():
def __init__(self):
print('object born,id:%s'%str(hex(id(self)))) # id(): 返回对象的内存地址

def f2():
while True:
c1 = ClassA()
c2 = ClassA()
c1.t = c2
c2.t = c1
del c1
del c2

f2()
  • 执行f2(),进程占用的内存会不断增大
  • gc模块唯一处理不了的是循环引用的类都有__del__方法,所以项目中要避免定义__del__方法

12. 参考资料

  • Garbage Collection in Python - GeeksforGeeks
  • 垃圾回收 · Python高级与网络编程
  • 剖析 Python 面试知识点- 内存管理和垃圾回收机制 - 云+社区 - 腾讯云
  • gc — Garbage Collector interface — Python 3.8.1 documentation
  • Program Language
  • Python
  • Advanced
Python函数式编程
Python厨书笔记-6
  1. 1. 1. 概述
  2. 2. 2. 具体实现
  3. 3. 3. Reference counting(引用计数)
  4. 4. 4. reference cycle(循环引用)
    1. 4.1. 5. 内存溢出
    2. 4.2. 6. 内存泄露
  5. 5. 7. Mark and Sweep(标记清除)
  6. 6. 8. generational GC(分代回收)
    1. 6.1. 9. 检测循环引用
  7. 7. 10. 具体实现
  8. 8. 11. 内存泄露
  9. 9. 12. 参考资料
© 2024 何决云 载入天数...