You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python2.7中函数内调用gc.get_objects为何引发循环引用/内存泄漏

问题背景

如下代码会产生循环引用,如果调用gc.disable()禁用自动垃圾回收,运行这段代码就会出现内存泄漏问题:

import gc, sys


def test_func():
    objs = gc.get_objects()
    print("-----End: test_func %s" % sys.getrefcount(test_func))

test_func()

已知该结论成立,但背后的原理不清晰,新增del objs的验证版本如下:

import gc, sys


def test_func():
    objs = gc.get_objects()
    del objs
    print("-----End: test_func %s" % sys.getrefcount(test_func))

test_func()

验证环境为Python 2.7。

核心原理

Python 2.7的内存管理以引用计数为核心,分代GC只做补充,专门处理引用计数搞不定的循环引用,这个问题的根因全在gc.get_objects()的返回特性和悄悄形成的引用环上:

  • 首先明确gc.get_objects()的行为:它会遍历返回当前解释器里所有被GC跟踪的对象,凑成一个列表返回,这个列表本身也是个被GC跟踪的容器对象。
  • 当函数内部执行objs = gc.get_objects()这一行时,会立刻生成两层引用计数无法处理的循环引用:
    1. 栈帧与列表互指:调用test_func时会生成对应的栈帧对象f,这个正在运行的栈帧本来就在GC跟踪范围内,自然会被塞进返回的objs列表,也就是列表l持有栈帧f的引用;反过来栈帧f的局部变量表中存储了objs变量,正好指向列表l,直接形成列表l -> 栈帧f -> 列表l的互指环。
    2. 列表自引用:Python 2.7中gc.get_objects()构造返回列表的过程中,会把刚创建的列表对象本身也加入结果集,相当于l自己持有自己的引用,这也是一种典型的循环引用。
  • 引用计数的判定规则非常简单:只要对象的引用计数大于0就不会被回收。正常情况下函数执行结束时,调用栈会释放对栈帧f的引用,栈帧销毁时会释放所有局部变量的引用,对应对象计数归0后自动回收。但因为上述循环引用的存在,回收流程直接卡壳:
    • 栈帧f除了调用栈持有的1个引用外,还被列表l持有1个引用,调用栈释放自身持有的引用后,f的引用计数仍为1,不会被销毁;
    • 列表l除了栈帧f持有的局部变量引用外,还被自己持有1个引用,就算栈帧释放了对l的引用,l的引用计数仍为1,不会被销毁。
  • 这类已经和外部代码完全断开、仅靠内部引用维持计数的对象环,只有在分代GC启动时,才会通过标记-清除算法识别并回收。如果手动调用gc.disable()关闭自动GC,这部分对象占用的内存永远不会被释放,就会产生内存泄漏。
  • 函数内部加del objs没有解决问题的原因也很简单:del objs只会删除栈帧局部变量表中对列表l的引用,但列表l的自引用依然存在,l的引用计数始终为1,还是无法被引用计数机制回收,泄漏问题不会消失。

补充:Python 3.x调整了gc.get_objects()的实现,返回的列表不会把自身纳入结果集,自引用问题不存在,但栈帧和列表的互指环依然存在,关闭GC运行照样会出现内存泄漏。

内容的提问来源于stack exchange,提问作者Grenter

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 21:24:20