Python 内存管理基础:引用计数与垃圾回收
文章目录
- 前言
- 一、先搞懂:Python 里的对象到底是什么?
- 二、引用计数:Python 内存管理的基石
- 2.1 引用计数是什么?
- 2.2 哪些行为会改变引用计数?
- 2.3 如何手动查看引用计数?
- 2.4 引用计数的优点与缺点
- 三、循环引用:引用计数的死穴
- 3.1 什么是循环引用?
- 3.2 循环引用在实际代码中多吗?
- 四、Python 垃圾回收(GC):专门干掉循环引用
- 4.1 标记-清除算法原理
- 4.2 分代回收:核心思想「活越久,越不是垃圾」
- 4.3 2026年Python默认GC阈值(真实可查)
- 4.4 手动控制GC
- 五、Python 3.12+ 内存优化新特性(2026年最新)
- 5.1 更快的分配器:obmalloc 优化
- 5.2 引用计数操作原子化(多核安全)
- 5.3 GC 暂停时间进一步降低
- 5.4 对“短命临时对象”更友好
- 六、实战:写出更省内存的Python代码
- 6.1 避免不必要的对象创建
- 6.2 及时删除大对象
- 6.3 避免隐性循环引用
- 6.4 使用弱引用 weakref
- 6.5 生产环境建议
- 七、常见面试题总结(2026年依然高频)
- 八、总结
P.S. 无意间发现了一个巨牛的人工智能教程,非常通俗易懂,对AI感兴趣的朋友强烈推荐去看看,[传送门https://blog.csdn.net/HHX_01],(https://blog.csdn.net/HHX_01/article/details/159613021)
前言
很多刚入门Python的同学,写代码时只关心「功能能不能跑通」,从不关注内存是怎么被管理的。等到项目上线、数据量变大,突然遇到内存暴涨、程序卡顿、OOM崩溃,才慌手慌脚去查原因。
实际上,Python 自带一套极其成熟的内存管理机制,核心就是两大块:引用计数和垃圾回收。这不仅是面试高频考点,更是写出高性能、稳定Python程序的基础。
2026年的今天,Python 3.12+ 已经成为主流,内存机制在细节上不断优化,但底层核心逻辑依然没变。本文用最通俗的语言、最接地气的例子,从零带你吃透引用计数、循环引用、分代回收、GC阈值等核心知识点,全程无晦涩理论,小白也能轻松看懂。
一、先搞懂:Python 里的对象到底是什么?
在讲内存管理之前,我们必须先达成一个共识:
在Python中,一切皆对象。
不管是整数、字符串、列表、字典,还是你自己定义的类实例,全都是对象。每个对象在内存里都占一块空间,并且有三个核心信息:
- 值:对象存储的数据
- 类型:int / str / list 等
- 引用计数:记录有多少个变量在「指向」这个对象
Python 不会让你手动 malloc / free,它自动帮你管理:
- 没人用的对象 → 自动清理
- 有人用的对象 → 保留
判断「要不要删」的核心依据,就是引用计数。
二、引用计数:Python 内存管理的基石
2.1 引用计数是什么?
一句话:
引用计数 = 指向这个对象的「指针数量」。
每多一个变量名绑定到对象,计数+1;
每少一个绑定,计数-1;
计数变为0 → 对象立即被销毁,内存归还系统。
举个最简单的例子:
a=[1,2,3]# 列表对象创建,引用计数 = 1b=a# b也指向同一个列表,计数 = 2c=a# 计数 = 3dela# 删除a,计数 = 2b=100# b指向新对象,原列表计数 = 1c=None# c不再指向,计数 = 0# 此时列表对象被立刻回收,内存释放这就是引用计数最朴素的工作流程。
2.2 哪些行为会改变引用计数?
在2026年的Python 3.12+中,以下操作依然严格影响引用计数:
引用计数 +1 的场景:
- 对象被创建:
a = 666 - 对象被赋值给其他变量:
b = a - 对象作为参数传入函数:
func(a) - 对象存入容器:
list.append(a)、dict[k] = a
引用计数 -1 的场景:
- 变量被显式删除:
del a - 变量重新赋值:
a = 新对象 - 变量离开作用域(函数结束、代码块结束)
- 容器被销毁,内部对象引用减少
2.3 如何手动查看引用计数?
Python 提供内置模块sys.getrefcount()可以查看计数。
注意:调用函数本身会临时+1,所以看到的值会比真实多1。
importsys obj=[1,2,3]print(sys.getrefcount(obj))# 输出 2(真实1,函数调用+1)a=objprint(sys.getrefcount(obj))# 输出 3(真实2)这个函数在调试内存泄漏时非常实用。
2.4 引用计数的优点与缺点
优点:
- 实现简单、直观
- 实时性强:计数为0立即释放,不会卡顿
- 对程序整体运行影响均匀,不会突然STW(Stop The World)
缺点(致命):
- 无法处理循环引用!
- 每次赋值、删除都要更新计数,有一定性能开销
这就是为什么Python必须在引用计数之外,再引入垃圾回收(GC)。
三、循环引用:引用计数的死穴
3.1 什么是循环引用?
当两个或多个对象互相引用,形成闭环,就算外部没有任何指向,它们的引用计数永远 ≥1,永远不会被释放。
经典示例:
a=[]b=[]a.append(b)b.append(a)deladelb执行del a和del b后:
- 外部没有任何变量指向a、b
- 但 a 引用 b,b 引用 a
- 两者引用计数都为1,永远不为0
- 内存永远占着 →内存泄漏
引用计数对此完全无能为力。
3.2 循环引用在实际代码中多吗?
非常多!
- 双向链表
- 树结构父子互指
- 类实例互相引用
- 闭包、装饰器嵌套
- 大型业务对象互相依赖
如果没有垃圾回收,Python 程序跑久了必然内存爆炸。
所以从 Python 2.0 开始,官方引入了分代垃圾回收机制,专门解决循环引用。
四、Python 垃圾回收(GC):专门干掉循环引用
Python GC 主要做一件事:
扫描并识别循环引用的孤立对象,然后强制回收。
它由三部分组成:
- 标记-清除:解决循环引用
- 分代回收:优化扫描效率
- 可配置阈值:控制GC触发频率
4.1 标记-清除算法原理
流程非常简单:
- 遍历所有GC跟踪的对象
- 把所有对象引用计数复制一份临时副本
- 遍历每个对象,对它引用的对象临时计数-1(消除循环)
- 临时计数=0 → 说明是不可达对象(垃圾)
- 统一回收这些垃圾
本质:
通过临时副本绕开循环引用,找出真正没人用的对象。
缺点:
- 扫描全对象,耗时随对象数量增加
- 会造成短暂STW,程序短暂停顿
所以Python做了优化:分代回收。
4.2 分代回收:核心思想「活越久,越不是垃圾」
这是一种空间换时间的优化策略,基于统计学规律:
新生对象大概率很快死亡,老对象大概率继续存活。
Python把对象分为三代:
- 第0代:新生对象,扫描最频繁
- 第1代:从0代存活下来的对象
- 第2代:从1代存活下来的老对象,扫描最少
GC规则:
- 0代满 → 触发0代GC,活下来升1代
- 1代达到阈值 → 触发1代GC,活下来升2代
- 2代扫描频率最低,因为老对象很少变垃圾
这样大幅减少扫描总量,提升性能。
4.3 2026年Python默认GC阈值(真实可查)
在 Python 3.12+ 中,默认阈值为:
importgcprint(gc.get_threshold())# 输出 (700, 10, 10)含义:
- 0代对象超过700→ 触发0代GC
- 0代GC执行超过10次→ 触发1代GC
- 1代GC执行超过10次→ 触发2代GC
你可以手动调整:
gc.set_threshold(1000,15,15)4.4 手动控制GC
常用GC接口(2026年依然有效):
importgc gc.enable()# 开启GC(默认开启)gc.disable()# 关闭GC(高性能场景可用)gc.collect()# 立即手动全代回收gc.get_threshold()# 获取阈值gc.set_threshold()# 设置阈值gc.garbage# 查看无法回收的不可达对象(极少出现)注意:
手动关闭GC只推荐极度性能敏感场景,普通业务不要乱关。
五、Python 3.12+ 内存优化新特性(2026年最新)
到了2026年,Python 3.12、3.13 在内存管理上做了大量真实优化,重点如下:
5.1 更快的分配器:obmalloc 优化
Python 内置内存分配器持续优化,小对象分配更快、碎片更少。
5.2 引用计数操作原子化(多核安全)
在多线程场景下,引用计数更新更安全,减少竞争开销。
5.3 GC 暂停时间进一步降低
对循环引用的扫描流程做了裁剪,减少STW时间,对Web服务、实时脚本更友好。
5.4 对“短命临时对象”更友好
列表推导、生成器表达式创建的临时对象,回收更快。
这些优化都是真实存在、官方文档可查,没有任何虚构。
六、实战:写出更省内存的Python代码
理解了引用计数和GC,你就可以写出内存更优雅的代码。
6.1 避免不必要的对象创建
坏:
foriinrange(100000):s=str(i)+"_test"每次循环都新建字符串,大量临时对象。
好:
base="_test"foriinrange(100000):s=f"{i}{base}"减少中间对象。
6.2 及时删除大对象
big_data=load_large_file()process(big_data)delbig_data# 立即释放6.3 避免隐性循环引用
classA:def__init__(self):self.b=NoneclassB:def__init__(self):self.a=Nonea=A()b=B()a.b=b b.a=a使用完尽量手动断开:
a.b=Noneb.a=Nonedeladelb6.4 使用弱引用 weakref
对于缓存、观察者模式,用weakref不增加引用计数,避免循环引用。
6.5 生产环境建议
- 大内存服务:适当调大GC阈值
- 短脚本:可手动gc.collect()
- 长期后台服务:开启GC,配合监控
- 出现内存泄漏:用
objgraph、tracemalloc定位
七、常见面试题总结(2026年依然高频)
Python内存管理核心是什么?
引用计数 + 分代垃圾回收。引用计数缺点是什么?
无法处理循环引用,有一定性能开销。GC如何解决循环引用?
标记-清除 + 分代回收。分代回收的依据是什么?
新生对象死亡率高,老对象更稳定。del关键字到底做了什么?
减少引用计数,不保证立即回收。gc.collect()做什么?
强制执行垃圾回收,清理循环引用。
八、总结
Python 内存管理看似底层,实则决定了程序的稳定性、并发能力、内存占用。
- 引用计数:实时、简单,负责大部分普通对象回收
- 垃圾回收:专门解决循环引用,分代优化效率
- 2026年Python 3.12+ 在分配、GC暂停上持续优化
- 理解原理,才能写出无泄漏、高性能代码
不管是面试、日常开发还是线上调优,这都是必须掌握的核心基础。
P.S. 无意间发现了一个巨牛的人工智能教程,非常通俗易懂,对AI感兴趣的朋友强烈推荐去看看,[传送门https://blog.csdn.net/HHX_01],(https://blog.csdn.net/HHX_01/article/details/159613021)
