📄 一句话概括
del 之后对象被回收;两个对象互相引用时引用计数不管用,gc.collect() 又回收了 2 个。
📝 本节和前面几节的关系
5.8 说每块堆都要有人收,这一节讲谁收、怎么收。
一、孤立=没人指
没有任何变量指向它,它就是孤立的。孤立的对象永远用不到了。 四个区的规则在这里合流:栈上的名字随函数消失,堆上的块靠计数判断。 所以【回收】的判断依据只有一条:还有没有箭头指向它。
二、实测
n = Node() 时引用计数 2(n 和传参);del n 之后对象已被回收。
用 weakref 看更清楚:删掉强引用后,弱引用立刻变成 None。
这一步是立即的,不需要任何回收动作——这就是引用计数的好处。
三、环的死穴
a.peer = b、b.peer = a,然后 del a, b——两块都还指着对方,计数不为 0。
实测 gc.collect() 又回收了 2 个对象,正是这一对环。
所以只有引用计数是不够的,必须有第二套机制扫环(分代回收)。
四、两套机制配合
引用计数管日常:快、立即,覆盖绝大多数对象。 分代回收管漏网:慢、定期,专门对付环。 代价是偶尔停顿——这就是 4.17 说的【垃圾回收不是免费的】。 这一章到此闭环:类一份(5.7)、实例 N 块(5.8)、没人指就收(5.13)。
五、动手 Demo:孤立的实例由垃圾回收处理
用弱引用看对象什么时候真的消失。 再造一对互相引用的对象,看引用计数为什么救不了它们。
import gc, weakref
n = Node()
r = weakref.ref(n)
del n
print(r() is None) # True:已被回收
a.peer, b.peer = b, a
del a, b
print(gc.collect()) # 又回收了这一对环
实跑输出:
--------------------------------------------------------------------
孤立的实例由垃圾回收处理
--------------------------------------------------------------------
【孤立】=没有任何变量指向它。**这种实例由垃圾回收负责收掉。**实测:
n = Node()
n 的引用计数 2
这时对象还在吗 在
del n 之后还活着吗 已被回收
再试循环引用:a.peer = b、b.peer = a,然后都 del 掉
gc.collect() 又回收了 2 个对象(主要是这一对环)
情况 引用计数能处理吗 靠什么
---------------------------------------------------
没人指向它 能,计数归零即回收 引用计数
两个对象互相指 不能,计数不为零 分代回收
**所以【垃圾回收】不是一件事,是两套机制配合:**
**引用计数管日常(快、立即),分代回收管漏网的环(慢、定期)。**
**孤立实例的内存就这样被【自动】收回——程序员不用写 free,代价是偶尔停顿。**
实测:del 之后弱引用变 None(立即回收);互相引用的两个对象要靠 gc.collect(),它回收了 2 个。 引用计数管日常(快、立即),分代回收管漏网的环(慢、定期)——两套机制配合。
30 秒实验
- 用
weakref观察一个对象在你del之后还剩不剩。 - 想一想:为什么缓存里存对象容易把内存吃满。
常见误区
- 以为有垃圾回收就不用管内存。全局缓存、循环引用照样能漏。
- 以为回收是立即的。环要靠定期扫描,可能要等一会儿。
自测三问(点开对答案)
1. 什么叫孤立实例? —— 没有任何变量指向它的实例——永远用不到了,回收的依据就是还有没有箭头指着它。 2. 实测两件事各是什么? —— del 之后弱引用立刻变 None(引用计数立即回收);互相引用的一对要靠 gc.collect(),回收了 2 个。 3. 两套机制怎么配合? —— 引用计数管日常(快、立即),分代回收管漏网的环(慢、定期),代价是偶尔停顿。- ☐ 下一节(COLUMN 编程往事 OOP中dump看起来很费劲?)
相关笔记
- 进化的OOP结构之三:垃圾回收 — 上一章的同一件事
- 每次创建实例都会使用堆区 — 这些块是怎么来的
- 编程往事 OOP中dump看起来很费劲? — 当年调试这些对象的苦
- 🧩 面向对象是怎样工作的 · 索引