Python 会自动回收内存,一般情况下不用关心内存的申请和释放问题。事实上我也一直没怎么关心过Python的内存管理问题,直到我用了 Python Prompt Toolkit 。这是一个 Python 的CLI组件库,使用简单,效果很好。只是性能用点差,另外就是它居然有内存泄漏。
内存问题产生原因
Python里内存管理主要基于引用计数实现,另外会辅以全图遍历以解决循环引用问题。一般内存问题都是对象被全局变量直接或间接持有导致。出现内存泄漏后关键是找到问题对象到底被谁给持有了。
确认内存泄漏的对象
如果一个程序内存一直异常增长,那多半是存在内存泄漏。接下来就是定位问题了。Python内存分析的工具和手段主要有下面几个:
- objgraph 可用现实对象的增长情况。还可以根据对象的引用关系生成图像。
- 可以根据对象生成引用关系树以及被引用关系树。第一感觉功能很强,实际用下来效果一般。对于复杂一些的项目,生成的关系树是在太过复杂。你以为对象都是通过属性持有,实际上各类的闭包,函数指针等都会持有对象。
- pympler 感觉和objgraph差不多,不支持生成图像。
gc.get_referents()
/gc.get_referents()
/gc.*
获取对象的引用计数及指向该对象的对象,以及其它分析函数。- 其它内存分析的库应当都是基于Python的gc模块实现。
print('PromptSession count: ', len([o for o in obj if isinstance(o, PromptSession)]))
打印对象数量,确认是否被释放。
解决内存泄漏问题
要解决内存问题,关键还是找到存在内存泄漏的问题被谁给持有里,然后在需要销毁对象时释放该持有。如果想该对象持有不影响对象的生命周期(比如缓存),可以使用 weakref 库来创建弱引用。
Python Prompt Toolkit 的内存问题
出于性能等考虑 Python Prompt Toolkit
添加来大量的缓存。其中一些看似简单的缓存对象持有了其它对象的函数(函数指针),从而间接持有了其它对象,最终导致大量的对象未被释放。一般情况下一个程序只有一个 PromptSession
对象,该对象贯穿程序的整个生命周期,因此问题不容易察觉。但我的应用时一个服务端程序,需要反复创建和销毁 PromptSession
对象,问题将出现了。
我尝试用 weakref.WeakValueDictionary
改写它的缓存实现,实际过程中发现key和value都会持有对象。
目前的做法是用户断开服务器连接时进行一次缓存的清理。