|
| 1 | ++++ |
| 2 | +date = '2025-09-02T8:00:00+08:00' |
| 3 | +draft = false |
| 4 | +title = 'Dealing With Grabage in Python' |
| 5 | +tags = ['Python'] |
| 6 | ++++ |
| 7 | + |
| 8 | + |
| 9 | +## Grabage Collection In Python |
| 10 | +本篇文章介绍 Python 中的 Grabage Collection (GC) 机制介绍 |
| 11 | + |
| 12 | +### What's Python Object? |
| 13 | +Python 对象中有三样东西: 类型(Type)、值(value)和引用计数(reference count), 当给变量命名时, Python 会自动检测其类型, 值在定义对象时声明, 引用计数是指该对象名称的数量. |
| 14 | + |
| 15 | +首先来看一个类 |
| 16 | +```Python |
| 17 | +class Person: |
| 18 | + def __init__(self, name, unique_id, spouse): |
| 19 | + self.name = name |
| 20 | + self.unique_id = unique_id |
| 21 | + self.spouse = spouse |
| 22 | + |
| 23 | + def __del__(self): |
| 24 | + print( |
| 25 | + # !r: 调用 repr() 来获取该对象的字符串表达式 |
| 26 | + # !s: str() |
| 27 | + # !a: ascii() |
| 28 | + f"Object {self.unique_id!r} is about to be removed from memory. Goodbye!" |
| 29 | + ) |
| 30 | +``` |
| 31 | +该 `Person` 类有以下3个属性: |
| 32 | +- `name`: 人名 |
| 33 | +- `unique_id`: 唯一性 id |
| 34 | +- `spouse`: 将为 `None` 或者将存储另一个 `Person` 对象 |
| 35 | + |
| 36 | +有一个特殊方法 `__del__()`, 这个特殊方法有一定的误导性. 该方法并不像 `__len__()` 与 `len()` 或者 `__iter__()` 与 `iter()` 那样与 `del` 关键字相关联. `__del__()` 特殊方法并不定义当对对象引用时 `del` 会发送什么, 相反, `__del__()` 是一个终结器 finaliser: 它在对象被消毁之前从内存中移除之间被调用. |
| 37 | +因此, `__del__()` 中 `print()` 调用的字符串仅在 Python 即将从内存中移除对象时显示. |
| 38 | + |
| 39 | +- 注意: `del` 方法并不会直接删除该对象, 而是会删除该对象的引用 |
| 40 | + |
| 41 | + |
| 42 | +### Reference Counting 引用计数 |
| 43 | +在引用计数中, 引用总是被统计并存储在内存中, 如下示例: |
| 44 | +```Python |
| 45 | +a = 50 |
| 46 | +b = a |
| 47 | +c = 50 |
| 48 | + |
| 49 | +print(id(a)) |
| 50 | +print(id(b)) |
| 51 | +print(id(c)) |
| 52 | + |
| 53 | +print(a is b) |
| 54 | +print(c is b) |
| 55 | +print(a is c) |
| 56 | + |
| 57 | +# 输出: |
| 58 | +134367443832424 |
| 59 | +134367443832424 |
| 60 | +134367443832424 |
| 61 | + |
| 62 | +True |
| 63 | +True |
| 64 | +True |
| 65 | +``` |
| 66 | +该示例的 `id` 都是相同的, 即为同一个变量, 此时引用计数为3, 如果使用 `del` 删除 `a` 和 `b`, `c` 仍然会存在, 因此此时引用计数为 1 |
| 67 | + |
| 68 | +引用计数 |
| 69 | +- 优点: 易于实现, 无需手动管理内存 (之所以引用计数, 就是因为py中变量赋值是增加一个引用, 而不是 c++/java 那样去内存中复制一个相同的对象) |
| 70 | +- 缺点: 引用计数的对象存储在内存中, 对内存管理不利; 此外, 无法处理循环引用的问题 |
| 71 | + |
| 72 | +例如下面这种最简单的循环引用: |
| 73 | +```Python |
| 74 | +a = [] |
| 75 | +a.append(a) |
| 76 | +print(a) |
| 77 | + |
| 78 | +# 输出 |
| 79 | +[[...]] |
| 80 | +``` |
| 81 | + |
| 82 | +该对象 `a` 循环引用其自身, 无法靠引用计数法删除 |
| 83 | + |
| 84 | +### Generational Garbage Collection 分代回收 |
| 85 | +分代垃圾回收是一种基于追踪系统 trace-based 的垃圾回收, 它可以打破循环引用并删除未使用的对象. |
| 86 | +Python 跟踪内存中的每个对象, 程序运行时创建3个列表: 第0代、第1代和第2代. |
| 87 | +新创建的对象被放入第0代列表, 会创建一个待丢弃对象的列表, 检测循环引用. 如果一个对象没有外部引用, 就会被丢弃. 在此过程中, 存活下来的对象被放入第1代列表, 相同的步骤应用于第1代列表. 从第1代列表中存活下来的对象被放入第2代列表. 第2代列表中的对象会一直保留到程序执行结束. |
| 88 | + |
| 89 | +Python 与其他编程语言的垃圾回收对比 |
| 90 | + |
| 91 | +垃圾回收在不同的编程语言中工作方式不同, 以下是 Python 的垃圾回收机制与其他常见编程语言的比较: |
| 92 | + |
| 93 | +- Python: 自动, 通常基于内存中对象的引用计数, 当对象的引用计数达到零时, 对象会自动被垃圾回收 |
| 94 | +- Java: 自动, 当堆内存接近满时(即当老年代堆空间达到一定大小时)或经过一定时间后, 它会垃圾回收不再使用的对象 |
| 95 | +- JavaScript: 自动, 通常使用标记-清除算法进行自动垃圾回收, 该算法标记可达或正在使用的对象, 并自动清除未标记的对 |
| 96 | +- C++: 非自动, 必须通过手动分配和释放对象内存来完成垃圾回收 |
| 97 | +- Rust: 没有垃圾回收(或者说是编译时垃圾回收), Rust 确实实现了一种自动内存管理机制, 它通过一种独特的所有权系统在编译时就确保内存安全, 从而避免了在运行时进行垃圾回收的需要 |
0 commit comments