Skip to content

Python学习笔记-gc垃圾回收

Python 的垃圾回收(Garbage Collection,GC)机制主要包括引用计数、标记 - 清除、分代回收这三部分,以下是详细介绍:

引用计数

引用计数是Python最基本的垃圾回收方式,Python为每个对象维护一个引用计数,记录对象被引用的次数。 - 原理: - 当一个对象被创建并赋值给一个变量时,对象的引用计数加1,如 a = [1, 2, 3],列表 [1, 2, 3] 的引用计数变为1。 - 当对象被其他变量引用时,引用计数再加1,比如 b = a,此时列表 [1, 2, 3] 的引用计数变为2。 - 当对象的引用被删除(变量被销毁或者变量重新赋值),引用计数减1,例如 del a,列表 [1, 2, 3] 的引用计数变为1 。 - 当对象的引用计数变为0时,Python解释器会立即回收该对象所占用的内存空间。 - 优点: - 实时性:能即时回收不再使用的对象,内存管理的响应速度快,不会造成大量内存碎片。 - 简单高效:实现原理简单,对大多数简单的对象回收场景,开销较小。 - 缺点: - 无法处理循环引用:当对象之间存在循环引用时,尽管从外部来看这些对象已经不再被使用,但引用计数永远不会变为0,导致内存泄漏。例如两个对象互相引用:

class Node:
    def __init__(self):
        self.next = None

a = Node()
b = Node()
a.next = b
b.next = a
del a
del b

这里ab形成循环引用,即使外部不再有对它们的引用,它们占用的内存也无法通过引用计数回收。

标记 - 清除

标记 - 清除算法是为了解决引用计数无法处理循环引用问题而引入的。 - 原理: - 标记阶段:从一组被称为“根对象”(GC Roots)的对象集合开始,遍历整个对象图,标记所有从根对象可达的对象。根对象包括全局变量、函数栈中的局部变量等。 - 清除阶段:遍历整个堆内存,回收所有未被标记的对象,因为这些未被标记的对象意味着从根对象无法访问到,也就是不再被使用。 - 优点:有效解决了循环引用导致的内存泄漏问题。 - 缺点: - 暂停时间:在标记和清除过程中,需要暂停程序的执行,可能会对程序的实时性产生一定影响。 - 扫描开销:每次回收都需要遍历整个堆内存,对于大型程序来说,这会带来一定的性能开销。

分代回收

分代回收是基于对象存活时间的一种优化策略,Python将对象分为0代、1代、2代三个“代”。 - 原理: - 对象分代:新创建的对象都放在0代,当0代对象数量达到一定阈值(默认为700),会触发0代的垃圾回收,回收短命对象。存活下来的对象会被移到1代。 - 代际回收:每进行10次0代的垃圾回收,会触发1代的垃圾回收,回收较老的对象,存活下来的对象移到2代。每进行10次1代的垃圾回收,会触发2代的垃圾回收,回收所有对象。因为通常情况下,存活时间越长的对象,越有可能一直被使用,所以2代的扫描频率最低,以此来提高垃圾回收的整体效率。 - 优点: - 提高效率:通过对不同代的对象采用不同的回收频率,减少了不必要的扫描,提高了垃圾回收的效率。 - 适应对象生命周期特点:符合大多数程序中对象的生命周期模式,新创建的对象往往很快就不再使用,而老对象则更倾向于长期存活。 - 缺点: - 复杂度增加:分代回收机制增加了垃圾回收系统的复杂度,需要维护不同代的对象集合和回收策略。 - 参数调整:不同的应用场景可能需要调整分代回收的阈值等参数,如果参数设置不当,可能会影响垃圾回收的效果和性能。

流程图

               ┌───────────────────────────┐
               │   对象创建 (new object)    │
               └─────────────┬─────────────┘
                             │
                             ▼
                   ┌──────────────────┐
                   │ 引用计数机制      │
                   │ (Reference Count)│
                   └───────┬──────────┘
                           │
       ┌───────────────────┼───────────────────┐
       │                   │                   │
       ▼                   ▼                   ▼
引用计数 > 0        引用计数 = 0        循环引用 (A↔B)
(继续存活)          (立即释放对象)       (引用计数无法归零)
                                               │
                                               ▼
                              ┌─────────────────────────┐
                              │   标记-清除 (Mark-Sweep) │
                              │   (检测不可达对象)       │
                              └─────────────┬───────────┘
                                            │
                                            ▼
                              ┌─────────────────────────┐
                              │   分代回收 (Generational)│
                              │   三代: 0代/1代/2代     │
                              └─────────────┬───────────┘
                                            │
          ┌─────────────────────────────────┼────────────────────────────────┐
          ▼                                 ▼                                ▼
   ┌─────────────┐                  ┌─────────────┐                  ┌─────────────┐
   │   0代 (young)│──幸存对象晋升──▶ │   1代 (middle)│──幸存对象晋升──▶ │   2代 (old)   │
   └───────┬─────┘                  └───────┬─────┘                  └───────┬─────┘
           │                                │                                │
阈值触发(默认700) → 回收短命对象   每10次0代GC触发1次   每10次1代GC触发1次
           │                                │                                │
           ▼                                ▼                                ▼
   ┌─────────────┐                  ┌─────────────┐                  ┌─────────────┐
   │  0代 GC     │                  │  1代 GC     │                  │  2代 GC     │
   │  快速清理   │                  │  清理更老对象│                  │  全面清理   │
   └─────────────┘                  └─────────────┘                  └─────────────┘

相关的Python标准库函数

在Python中,可以使用gc模块来操作和控制垃圾回收机制: - gc.enable():启用垃圾回收机制。 - gc.disable():禁用垃圾回收机制,一般不建议在正常开发中使用,可能会导致内存泄漏。 - gc.collect([generation]):手动触发垃圾回收,可以指定代际,如gc.collect(2) 表示触发2代的垃圾回收。 - gc.set_threshold(threshold0[, threshold1[, threshold2]]):设置分代回收的阈值,如gc.set_threshold(700, 10, 10)