Ubuntu 14.04下Python嵌套列表为何出现内存泄漏?
问题分析与解决方案
首先,你遇到的不是真正的内存泄漏,而是Python 2.7结合旧版Linux glibc(Ubuntu 14.04使用的glibc 2.19)的内存分配器行为导致的"内存保留"现象,属于典型的高水位线(high-water mark)问题。下面详细拆解原因和解决办法:
为什么会出现这种情况?
Python 2的小对象内存池机制
Python 2使用pymalloc作为小对象(小于256字节)的分配器,它会从操作系统一次性申请大块内存,再自行拆分给小对象使用。当你创建10^7个包含浮点数的小列表时,会触发大量小对象分配,直接导致内存池规模膨胀。
当你执行del temp和gc.collect()后,Python确实释放了这些对象的内存,但pymalloc并不会立刻把所有空闲内存归还给操作系统——它会保留这些内存池,以便后续小对象分配时快速复用,避免频繁向OS申请内存的开销。旧版glibc的内存回收策略
Ubuntu 14.04的glibc 2.19使用的ptmalloc2分配器,在内存归还逻辑上比较保守:只有当内存池中的空闲块足够大且连续时,才会归还给操作系统。而嵌套列表[[0.1]]释放后,内存池里的空闲块往往比较零散,无法满足glibc的归还条件,导致内存被保留下来,表现为常驻内存居高不下。不同环境的差异原因
- OSX无此问题:OSX使用的libmalloc内存分配器在内存回收时更积极,会主动将空闲内存归还给系统,不会保留过多闲置内存。
- Python3无此问题:Python3对
pymalloc做了大量优化,调整了内存池的回收策略,空闲内存较多时会主动归还给操作系统;同时Python3的对象结构设计更高效,从根源减少了这类内存保留的情况。 [[] for ...]或[0.1 for ...]无问题:空列表的内存占用极小,释放后内存池的空闲块更容易合并成大块,触发glibc的归还逻辑;而纯浮点数列表是连续分配的单一类型对象,释放后内存块更规整,也更容易被归还给系统。
已知解决方案
- 切换到Python3:这是最彻底的解决办法,Python3从根本上优化了内存管理机制,完全避免了这类问题。
- 手动调用glibc的内存修剪函数:在Python2中,可以通过
ctypes调用glibc的malloc_trim(0),强制让分配器把空闲内存归还给操作系统:
调用后你会看到常驻内存明显下降到接近初始值。import gc import ctypes temp = [[0.1] for _ in xrange(10 ** 7)] del temp gc.collect() # 强制修剪内存池,归还空闲内存给系统 libc = ctypes.CDLL("libc.so.6") libc.malloc_trim(0) - 使用更高效的数据结构:如果业务场景允许,改用numpy数组等紧凑的数据结构代替嵌套列表,不仅内存占用更低,还能避免触发小对象内存池的膨胀问题:
import numpy as np temp = np.full((10**7, 1), 0.1) - 调整Python环境变量:可以尝试设置
PYTHONMALLOC=malloc强制Python使用系统的malloc而非pymalloc,但这可能会降低小对象分配的性能,需要根据实际场景权衡。
内容的提问来源于stack exchange,提问作者Tim Martin
相关产品推荐
相关产品推荐

