⌨️ 第四章 流程图与算法
📄 一句话概括

用完的内存不还=内存泄露(房子越占越多直到系统卡死);谁来还?垃圾回收(GC)自动收房——C 程序员亲手还(free),Python/Java 自动还(引用计数+分代回收),但自动收房也有收不到的死角:你还攥着房号。

📝 本节和前面几节的关系

3-14 讲了【要房、住人】(内存分配与指针),这一篇讲【退房】。回收 4-6 的作用域(局部变量随栈弹出自动释放)与 3-16 的栈——栈上自动清,堆上靠 GC。

一、内存泄露:房子不还

程序向系统申请内存(C 的 malloc、Python 的对象创建),用完该还。不还的下场:

// C 语言的经典泄露
char* buf = malloc(1024);   // 要了一间房
buf = malloc(2048);         // 又要一间 —— 旧的房号被覆盖,1024 那间永远没人还

一次泄露 1KB 没感觉;服务器循环里每次泄露 1KB、跑一天——内存条被喝干,系统卡死、进程被杀。泄露的本质:内存还在被占用,但再也没人能找到它去还——不是【丢了】,是【赖着没人管】。

二、垃圾回收:自动收房的两种策略

C/C++:手动管理——malloc/free(或 new/delete)亲力亲为。权力大:精确控制时机;事故多:忘 free(泄露)、重复 free(崩)、用已还的房(野指针,回收 3-14)。

Python/Java:自动垃圾回收——运行时盯着对象,【没人用了】就收:

三、Python 里照样能【制造泄露】

GC 管的是【没人引用】的垃圾——你还攥着引用,它就不是垃圾:

cache = []
def process(data):
    result = data * 2
    cache.append(result)     # 只进不出:列表越攒越大
    return result

for i in range(1_000_000):
    process(i)               # 内存蹭蹭涨——不是 GC 失职,是你赖着不放

这类【逻辑性泄露】是 Python 内存问题的主体:越攒越大的全局列表/字典、忘了关的文件与连接、循环引用+带 del 的对象。诊断工具:tracemalloc(看内存涨在哪)。

四、动手 Demo:引用计数现形

import sys
a = [1, 2, 3]
print(sys.getrefcount(a))    # 2(a + getrefcount 的临时参数)

b = a                        # 两个人指同一对象
print(sys.getrefcount(a))    # 3
del b                        # 撤一个引用
print(sys.getrefcount(a))    # 2 —— 回到 2;a 和 b 全撤(计数归零)对象立刻回收

看到什么算成功:2、3、2。

读结果:计数随引用增减——引用计数归零的那一刻对象就地回收(CPython 即时性)。计数多出的 1 是 getrefcount 调用本身的临时引用,别大惊小怪。

五、30 秒实验

跑:

import tracemalloc
tracemalloc.start()
leak = []
for i in range(100_000):
    leak.append("x" * 100)
current, _ = tracemalloc.get_traced_memory()
print("占用约", current // 1024, "KB")   # 约 10 万 x 100 字节 ≈ 9765 KB

内存涨在明处——把 leak.append 那行删掉再跑,几乎不涨。30 秒学会第一件内存诊断工具。

六、常见误区

  1. 【有垃圾回收就不会内存泄露】——错。GC 只收【摸不到】的对象;全局列表越攒越大、字典只增不删、闭包攥着大对象——你摸得到的东西 GC 永远不碰,逻辑性泄露照样发生。
  2. 【Python 的 del 是删数据】——错。del 删的是引用(名字);最后一个引用没了对象才回收——【删名字】与【拆房子】是两步。
  3. 【手动管理是落后的设计】——分场景。C 的手动管理换来确定性(嵌入式、实时系统必须);GC 换来安全省心但偶尔停顿(回收时可能暂停程序)——工程取舍,无高下。
自测三问(点开对答案) 1. **内存泄露的本质是什么?** 答:内存仍被占用但再也无法被找到归还——不是数据丢了,是【赖着且无人能管】。 2. **CPython 的两大回收机制及分工?** 答:引用计数(归零立刻回收,主力)+分代回收(补刀收互相引用的孤岛,按存活时间分级检查)。 3. **为什么 Python 也可能内存泄露?** 答:GC 只收摸不到的对象;全局容器只增不删、连接不关、闭包攥着大对象——引用还攥着,GC 永远不碰。

相关笔记