题目重点:区分业务持续持有、循环对象和原生分配,按稳定负载定位增长,避免只看RSS断言泄漏。
题意与假设
这道练习题保留了面经中值得训练的核心约束,并将需要澄清的条件单独列出。现场拿到变体时,先和面试官确认这些条件。
- 语言层行为与解释器实现分开;CPython引用计数/循环GC不当作所有Python实现保证。
- 诊断示例只用内存对象,不提供某生产应用已有泄漏的结论。
解题路线
- 有GC仍会出现逻辑泄漏:全局缓存、队列、回调注册、日志/异常引用持续保持本应释放对象可达,垃圾回收器不能删除仍可达的业务状态。
- 循环引用并不自动等于永久泄漏,支持循环GC的实现能回收很多不可达循环。反之可达对象即使没有循环也不会被回收;排查不能只搜两个对象互相引用。
- 建立可重复负载与稳定阶段,观察活跃对象数、分配栈和保留引用链,区分一次性预热、无界缓存、批次堆积与正常峰值。单次RSS高不证明对象还活着,分配器可能保留内存供复用。
- Python层工具可跟踪分配和对象引用,原生扩展、GPU缓冲或文件连接可能在其观察范围外;分别测Python heap与进程/外部资源指标,不把看不到的分配宣称不存在。
- 治理按原因:缓存限制容量/TTL,队列做背压,回调取消注册,context manager关闭文件/连接,避免永久保存traceback或大闭包;weakref只适合允许对象自然消失的关系,不是强行解所有泄漏。
- 测试反复创建/释放后是否趋稳,配合weakref验证特定对象可回收,再检查异常和取消路径。不要在每次请求强行gc.collect代替修正持有关系,其暂停还会伤尾延迟。
手动走一遍
对象self指向自己后删除外部名字,支持循环GC的运行时可在collect后释放;同对象仍留在cache列表中则collect不会释放,清空cache后才可能回收。示例已用weakref核对两条路径。
规模与取舍
引用追踪和完整GC可能扫描大量对象,成本取决于存活图与实现;缓存空间受容量约束才能有界。诊断应记录稳定负载的增长率与尾延迟,而非宣称统一O(1)。
易错点与追问
容易失分的边界
- Python有GC所以不可能泄漏。
- 循环引用必然永不回收。
- RSS不下降就确定泄漏。
继续深挖
- 原生扩展泄漏怎么分层定位?
- 异常traceback为什么能保留大对象?
- 缓存/队列怎样建立内存预算?
技术参考
- Python gc:技术语义参考,具体题目按本文约定。
