对象内存管理
python中对于对象内存管理有两种方法,引用计数/GC 。</br>
引用计数策略应用到每个对象的管理中,接收/返回对象都需要+-对象的计数,而对象是否支持GC则是可选的,因为GC的存在是为了解决引用计数留下的循环引用问题,对于没有包含其他对象指针的对象可以不支持GC。
引用计数
引用计数的优势在于简单,把对象销毁时间分摊到程序生命周期
static PyObject* PyPerson_New(PyTypeObject *type, PyObject *args, PyObject *kwds){
PyObject *ret = create_person(...);
//发生异常 销毁对象
if(PyErr_Occurred()){
Py_XDECREF(ret);
return NULL;
}
if(ret == NULL)
FAST_RETURN_ERROR("create person obj fail");
if(!check_person(ret)){
Py_XDECREF(ret); //销毁对象 -1
Py_XINCREF(Py_None); //返回对象 +1
return Py_None;
}
return ret;
}
上面的实例代码演示了返回对象计数+1 和 对象超出作用于计数-1
当对象计数==0的时候 调用typeobject的tp_dealloc函数完成对象清理
#define Py_DECREF(op) \
do { \
PyObject *_py_decref_tmp = (PyObject *)(op); \
if (_Py_DEC_REFTOTAL _Py_REF_DEBUG_COMMA \
--(_py_decref_tmp)->ob_refcnt != 0) \
_Py_CHECK_REFCNT(_py_decref_tmp) \
else \
_Py_Dealloc(_py_decref_tmp); \
} while (0)
当然引用计数也有众所周知的缺点,循环引用,所以还是需要引入GC机制来弥补
GC
python gc使用的策略是标记-清除,根据是否从root object可达判断一个对象是否是垃圾对象
对象支持GC
由于是否支持GC是可选的,所以我们要主动选择对象是否支持GC,只需要在typeobject中加入一个标记就好 Py_TPFLAGS_HAVE_GC
GC对象内存模型
gc对象内除了对象本身的数据,还增加了一些gc信息,具体可以看看gc对象内存分配过程:</br>
PyObject *
PyType_GenericAlloc(PyTypeObject *type, Py_ssize_t nitems)
{
//......
if (PyType_IS_GC(type))
obj = _PyObject_GC_Malloc(size);
else
obj = (PyObject *)PyObject_MALLOC(size);
//.....
}
static PyObject *
_PyObject_GC_Alloc(int use_calloc, size_t basicsize)
{
PyObject *op;
PyGC_Head *g;
size_t size;
if (basicsize > PY_SSIZE_T_MAX - sizeof(PyGC_Head))
return PyErr_NoMemory();
size = sizeof(PyGC_Head) + basicsize;
//....
}
可以看出gc对象内存模型如下:
————-----
|gc head|
|-------|
| obj |
| |
————-----
//通过PyGC_Head把gc对象形成链表
typedef union _gc_head {
struct {
union _gc_head *gc_next;
union _gc_head *gc_prev;
Py_ssize_t gc_refs; //gc对象的状态
} gc;
double dummy; /* force worst-case alignment */
} PyGC_Head;
/设置为untrack 未追踪
g->gc.gc_refs = 0;
_PyGCHead_SET_REFS(g, GC_UNTRACKED);
//把新对象统计在第0 代
_PyRuntime.gc.generations[0].count++; /* number of allocated GC objects */
//如果第0代对象数超过了阈值 触发gc
if (_PyRuntime.gc.generations[0].count > _PyRuntime.gc.generations[0].threshold &&
_PyRuntime.gc.enabled &&
_PyRuntime.gc.generations[0].threshold &&
!_PyRuntime.gc.collecting &&
!PyErr_Occurred()) {
_PyRuntime.gc.collecting = 1;
collect_generations(); //gc
_PyRuntime.gc.collecting = 0;
}
python gc也是分代,同样具有新生代、老年代的表现形式,和jvm gcheap 分代不同的是 这里的代只是统计意义不具备内存占用
注意到走完对象内存分配的流程,对象其实还没有真正的分配到某一代中
在PyObject_GC_Alloc中分配完内存之后才会执行这一步
if (PyType_IS_GC(type))
_PyObject_GC_TRACK(obj); //加入到对象链表
把对象加入到第0代的对象链表
#define _PyObject_GC_TRACK(o) do { \
PyGC_Head *g = _Py_AS_GC(o); \
if (_PyGCHead_REFS(g) != _PyGC_REFS_UNTRACKED) \
Py_FatalError("GC object already tracked"); \
_PyGCHead_SET_REFS(g, _PyGC_REFS_REACHABLE); \
g->gc.gc_next = _PyGC_generation0; \
g->gc.gc_prev = _PyGC_generation0->gc.gc_prev; \
g->gc.gc_prev->gc.gc_next = g; \
_PyGC_generation0->gc.gc_prev = g; \
} while (0);
GC过程
要筛选出垃圾对象,最直接的方法就是从rootobject开始把所有能访问到的对象都标记上,剩下的就是垃圾对象了。这个过程需要做两个事,1.确定哪些是rootobject 2.从rootobject遍历对象。</br>
确定GC范围
static Py_ssize_t
collect_generations(void)
{
int i;
Py_ssize_t n = 0;
//查找最老的 超出阈值的代
for (i = NUM_GENERATIONS-1; i >= 0; i--) {
if (_PyRuntime.gc.generations[i].count > _PyRuntime.gc.generations[i].threshold) {
//如果long_lived对象不是很多 则避免full gc
if (i == NUM_GENERATIONS - 1
&& _PyRuntime.gc.long_lived_pending < _PyRuntime.gc.long_lived_total / 4)
continue;
n = collect_with_callback(i); //收集 gen[i] - gen[0]
break;
}
}
return n;
}
对象遍历
在确定rootobject之前,我们要先解决对象遍历的问题。因为我们需要从一个对象开始访问它引用的对象,也就是广度优先遍历,所以不能直接遍历gc对象链表,而是使用额外的机制。
static void
subtract_refs(PyGC_Head *containers)
{
traverseproc traverse;
PyGC_Head *gc = containers->gc.gc_next;
for (; gc != containers; gc=gc->gc.gc_next) {
traverse = Py_TYPE(FROM_GC(gc))->tp_traverse;
(void) traverse(FROM_GC(gc),
(visitproc)visit_decref,
NULL);
}
}
这个遍历机制就是typeobject中的tp_traverse函数,在tp_traverse函数中对象必须把引用到的对象交给函数visitproc处理,这样就完成了对象的广度优先遍历。
static int person_traverse(PyObject *self, visitproc visit, void *arg){
Person *p = (Person*)self;
//visit(p->dict,arg)
Py_VISIT(p->dict);
return 0;
}
确定rootobject
所有对象和对象直接的引用形成了一个有向图,先把对象之间的引用去掉,那么最后计数>0的表明对象存在非对象间引用 也就是rootobject
接回上面的例子,visit_decref就是用来把对象中的引用-1的函数
static int
visit_decref(PyObject *op, void *data)
{
if (PyObject_IS_GC(op)) {
PyGC_Head *gc = AS_GC(op);
if (_PyGCHead_REFS(gc) > 0)
_PyGCHead_DECREF(gc);
}
return 0;
}
完成第一轮筛选后,把计数>0的标记未reachable,计数==0的标记为unreachable
static void
move_unreachable(PyGC_Head *young, PyGC_Head *unreachable)
{
PyGC_Head *gc = young->gc.gc_next;
while (gc != young) {
PyGC_Head *next;
//refs > 0 经过上面的refs-1 root object refs>0
if (_PyGCHead_REFS(gc)) {
PyObject *op = FROM_GC(gc);
traverseproc traverse = Py_TYPE(op)->tp_traverse;
assert(_PyGCHead_REFS(gc) > 0);
//设置对象为reachable
_PyGCHead_SET_REFS(gc, GC_REACHABLE);
//从这个rootobject 能访问到的对象都是 reachable
(void) traverse(op,
(visitproc)visit_reachable,
(void *)young);
next = gc->gc.gc_next;
if (PyTuple_CheckExact(op)) {
_PyTuple_MaybeUntrack(op);
}
}
else {
//unreachable 这里会误判 遍历的时候会修正
next = gc->gc.gc_next;
gc_list_move(gc, unreachable);
_PyGCHead_SET_REFS(gc, GC_TENTATIVELY_UNREACHABLE);
}
gc = next;
}
}
static int
visit_reachable(PyObject *op, PyGC_Head *reachable)
{
if (PyObject_IS_GC(op)) {
PyGC_Head *gc = AS_GC(op);
const Py_ssize_t gc_refs = _PyGCHead_REFS(gc);
if (gc_refs == 0) {
//计数+1 这样等下遍历到它就会归为 reachable
_PyGCHead_SET_REFS(gc, 1);
}
else if (gc_refs == GC_TENTATIVELY_UNREACHABLE) {
//上面遍历的时候误判了 把对象放回reachable链表
gc_list_move(gc, reachable);
_PyGCHead_SET_REFS(gc, 1);
}
}
return 0;
}
存活对象迁移
完成了reachable对象和unreachable对象筛选后,存活对象需要移动到老年代中
if (young != old) {
//如果是gen[1] 存活数到统计起来 这个会影响到full gc
if (generation == NUM_GENERATIONS - 2) {
_PyRuntime.gc.long_lived_pending += gc_list_size(young);
}
//存活对象进入到更老的gen
gc_list_merge(young, old);
}
else {
//如果是full gc 会untrack掉dict对象减轻gc负担
untrack_dicts(young);
_PyRuntime.gc.long_lived_pending = 0;
//对象进入long lived状态
_PyRuntime.gc.long_lived_total = gc_list_size(young);
}
距离真正完成对象筛选还是差最后一步,因为设计遗留问题如果对象实现了tp_del函数 会有一些麻烦。因为有的对象会在tp_dealloc、tp_free中调用引用对象的tp_del做清理,但是gc并不能保证A引用B,B一定比A销毁晚,如果B销毁了,A还调用B的tp_del会导致内存错误,所以实现了tp_del的对象会被放弃收集。为了让程序员有机会手动去清理这部分对象,gc会把这部分对象存放到garbage链表中。
gc_list_init(&finalizers);
//实现了tp_del的对象移动到finalizers链表
move_legacy_finalizers(&unreachable, &finalizers);
//设置为reachable
move_legacy_finalizer_reachable(&finalizers);
//存放到 garbage 链表 让程序员自己处理
handle_legacy_finalizers(&finalizers, old);
对象清理
static void
delete_garbage(PyGC_Head *collectable, PyGC_Head *old)
{
inquiry clear;
while (!gc_list_is_empty(collectable)) {
PyGC_Head *gc = collectable->gc.gc_next;
PyObject *op = FROM_GC(gc);
//定义了DEBUG_SAVEALL会导致不清除 而是存放到grabage链表
if (_PyRuntime.gc.debug & DEBUG_SAVEALL) {
PyList_Append(_PyRuntime.gc.garbage, op);
}
else {
if ((clear = Py_TYPE(op)->tp_clear) != NULL) {
//调用tp_clear
Py_INCREF(op);
clear(op);
Py_DECREF(op);
}
}
tp_clear要做的就是引用对象计数-1,把对象从unreachable移除,释放对象内存回内存池
static int person_clear(PyObject *self){
Person *p = (Person*)self;
Py_CLEAR(p->dict);
//PyObject_GC_Del
Py_TYPE(self)->tp_free(self);
return 0;
}