CPython向OS返还未使用RSS内存的具体触发条件是什么
CPython向操作系统归还未使用RSS内存的核心条件
CPython的内存归还逻辑不是单一层面决定的,需要同时满足解释器自身内存分配器的释放规则,以及底层系统内存分配器的归还条件,具体规则如下:
- CPython默认内置的
pymalloc分配器只负责管理小于512字节的小对象内存,大于这个阈值的内存申请会直接透传给操作系统层面的内存分配器(比如glibc malloc、jemalloc等),两类内存的归还逻辑完全不同。
pymalloc管理的小对象内存归还条件
pymalloc的内存按arena -> pool -> block三级结构组织:单个block对应特定大小类的对象存储单元,多个block组成和系统内存页大小对齐的pool(通常为4KB),多个pool组成默认大小256KB的arena,arena是pymalloc向OS申请内存的最小单位。
这类内存要归还到OS,必须满足:
- 对应arena内所有pool都完全空闲,没有任何仍在被引用的对象占用block。只要一个arena里还剩1个正在使用的block,整个arena的内存都会被pymalloc保留,不会归还给底层分配器。这一逻辑和你提到的PyPy内存归还规则类似,仅当整个arena完全空闲时才会向底层释放,区别是CPython的arena默认大小为256KB。
- 单个pool内的block全部被释放时,只会被标记为可用状态返回给所属arena的空闲池,不会触发内存归还。
大对象内存归还条件
超过pymalloc大小阈值的大对象(比如大列表、大字节串、长字典等),内存申请释放直接走系统级分配器:
- 当对象引用计数归0、或者被循环垃圾回收器回收后,CPython会立刻调用对应分配器的释放接口(比如
free())归还内存,不需要等arena全空。 - 注意这一步只是把内存交还给系统分配器,不代表内存立刻回到OS。
系统分配器层面的最终归还条件
就算CPython层面已经调用了释放接口,RSS是否真正下降,还取决于底层分配器的实现策略:
- 传统glibc使用的ptmalloc2分配器,只有当空闲内存块刚好位于进程堆的顶部(紧邻brk指针位置)时,才会通过调整brk指针把内存还给内核;如果空闲块在堆的中间位置,只会被放入分配器自己的空闲缓存链表,不会归还,RSS不会下降。
- tcmalloc、jemalloc等现代分配器的归还策略更积极,会定期扫描空闲内存块,通过
madvise(MADV_DONTNEED)类系统调用通知内核回收不再使用的物理页,即使内存块不连续也能完成归还,RSS下降会更明显,这部分逻辑完全由分配器控制,CPython无法干预。
垃圾回收的影响
- CPython以引用计数为主要回收机制,绝大多数无循环引用的对象在引用计数归0时就会被立刻释放,不需要等待GC运行。
- 存在循环引用的对象,必须等到分代GC扫描到并打破循环引用链之后,才会被真正回收,后续才有可能触发内存归还流程。如果循环引用的对象零散分布在多个arena中,会导致这些arena始终存在被占用的block,长期无法被释放回OS。
常见说明
- 手动调用
gc.collect()只能触发循环引用回收,无法强制把内存还给OS,回收完成后如果不满足上述arena全空、分配器可归还的条件,RSS不会下降。 - 对内存归还敏感度很高的场景,可以通过设置环境变量
PYTHONMALLOC=malloc完全禁用pymalloc,让所有内存分配直接走系统分配器,代价是小对象分配性能会有一定损耗。
内容的提问来源于stack exchange,提问作者Tjaart van der Walt
相关产品推荐
相关产品推荐

