Python中的内存管理涉及包含所有Python对象和数据结构的私有堆。Python内存管理器在内部确保对此私有堆的管理。需要注意的是,Python堆的管理是由解释器本身执行的,并且用户无法控制它。从源码来看,分为以下几层:
- level +3:内置类型分配器
- level +2:python对象分配器
- level +1:python原生内存分配器
- level 0:底层通用分配器
- level -1:特定于os的虚拟内存管理器
- level -2:物理内存管理器
由上可知,Python 中的对象管理主要位于 +1 ~ +3 层。对于大于512KB(可以设置更改)的对象,使用 +1层中的 Python 原生内存分配器(Python’s raw memory allocator)进行分配,其本质是调用 C 标准库中的 malloc/calloc/realloc/free
等函数;对于小于等于 512KB 的对象,使用+2层的Python 对象分配器(Python’s object allocator)实施。对于一些内置类型,如 int/dict/list/string 等,会有单独的针对这些内置类型的分配器实现。比如管理 int 类型就使用一个简单的 free list,这些分配器都位于 +3层。
在Python中,为了解决内存泄露问题,采用了对象引用计数(reference count),并基于引用计数实现自动垃圾回收。借助“标记-清除”机制消除循环引用带来的影响。引入“分代回收”机制减少“标记-清除”所带来的额外操作。
- 引用计数
# 计数增加 refcnt incr
#define Py_INCREF(op) (
_Py_INC_REFTOTAL _Py_REF_DEBUG_COMMA
((PyObject*)(op))->ob_refcnt++)
#计数减少 refcnt desc
#define _Py_DEC_REFTOTAL _Py_RefTotal--
#define _Py_REF_DEBUG_COMMA ,
#define Py_DECREF(op)do{if(_Py_DEC_REFTOTAL _Py_REF_DEBUG_COMMA
--((PyObject*)(op))->ob_refcnt!= 0)
_Py_CHECK_REFCNT(op)else
_Py_Dealloc((PyObject *)(op)); # refcnt变成0的时候, 会调用_Py_Dealloc}while(0)PyAPI_FUNC(void)_Py_Dealloc(PyObject *);#define _Py_REF_DEBUG_COMMA ,
#define _Py_Dealloc(op) (_Py_INC_TPFREES(op)_Py_COUNT_ALLOCS_COMMA
(*Py_TYPE(op)->tp_dealloc)((PyObject *)(op))) # 调用各自类型的tp_dealloc
#endif /* !Py_TRACE_REFS */
# Python基本类型的tp_dealloc, 通常都会与各自的缓冲池机制相关,释放会优先放入缓冲池中(对应的分配会优先从缓冲池取).这个内存分配与回收同缓冲池机制相关
# 当无法放入缓冲池时, 会调用各自类型的tp_free
# 当计数变为0时,使用tp_free函数进行内存销毁的操作
PyTypeObject PyType_Type= {
PyVarObject_HEAD_INIT(&PyType_Type,0)
"type", /* tp_name */
...
PyObject_GC_Del, /* tp_free */
};
- 内存回收
- 放入缓冲池:缓存部分反复创建和销除的对象,而非在它们释放后直接从内存删除它们,从而加速下次该对象的创建。
- 真正销毁,使用PyObject_Del/PyObject_GC_Del对内存进行操作
- PyObject_Del 不运行析构函数只释放内存。同PyObject_Free
-
PyObject_GC_Del
void
PyObject_GC_Del(void*op)
{
PyGC_Head *g= AS_GC(op);
# 如果跟踪给定对象,则返回true
if(IS_TRACKED(op)) # IS_TRACKED 涉及标记-清除机制
# 从跟踪链表中移除
gc_list_remove(g);
if(generations[0].count> 0){ # generations 涉及分代回收机制
generations[0].count--;
}
PyObject_FREE(g); # PyObject_FREE 涉及python底层内存池机制
}
- 标记-清除
- 分代回收
- 将系统中的所有内存块根据其存活时间划分为不同的集合, 每个集合就称为一个”代”。
- 一个代就是一个链表, 所有属于同一”代”的内存块都链接在同一个链表中。
- Python中总共有三个”代”,每个代的threshold值表示该代最多容纳对象的个数。默认情况下,当0代超过700,或1,2代超过10,垃圾回收机制将触发。0代触发将清理所有三代,1代触发会清理1,2代,2代触发后只会清理自己。
- 垃圾收集的频率随着”代”的存活时间的增大而减小(存活时间越长的对象, 就越不可能是垃圾, 就会减少其收集的频率)
- id() 查看该对象的内存地址;
- is 判断两个引用所指的对象是否相同;
- sys包中的getrefcount()来查看对象的引用次数。需要注意的是,当使用某个引用作为参数,传递给getrefcount()时,参数实际上创建了一个临时的引用。因此,getrefcount()所得到的结果,会比期望的多1。
http://ju.outofmemory.cn/entry/215604
Python 源码阅读:垃圾回收机制