当前位置: 首页 > 后端技术 > Python

深入理解PythonGC

时间:2023-03-26 14:36:00 Python

对象内存管理python中对象内存管理有两种方式,引用计数/GC。引用计数策略应用于每个对象的管理。接收/返回对象需要+-对象计数,对象是否支持GC是可选的,因为GC的存在是为了解决引用计数留下的循环引用问题,不包含指向其他对象的指针的对象可能不支持GC对象。引用计数引用计数的优点是简单,对象销毁时间分配到程序生命周期staticPyObject*PyPerson_New(PyTypeObject*type,PyObject*args,PyObject*kwds){PyObject*ret=create_person(...);//发生异常销毁对象if(PyErr_Occurred()){Py_XDECREF(ret);返回空值;}if(ret==NULL)FAST_RETURN_ERROR("createpersonobjfail");if(!check_person(ret)){Py_XDECREF(ret);//销毁对象-1Py_XINCREF(Py_None);//返回对象+1returnPy_None;}returnret;}上面的示例代码演示了当objectcount==0时返回objectcount+1和objectexceedoncount-1当调用typeobject的tp_dealloc函数完成对象清理时#definePy_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_De分配(_py_decref_tmp);\}while(0)当然引用计数也有众所周知的缺点,比如循环引用,所以还是需要引入GC机制来弥补GCpythongc使用的策略是mark-clear,判断一个对象基于是否从根对象可达对象是否支持GC是可选的,所以我们要主动选择对象是否支持GC。我们只需要在类型对象中添加一个标记即可。Py_TPFLAGS_HAVE_GCGC对象内存模型。补充了一些gc信息,可以看看gc对象内存分配过程:PyObject*PyType_GenericAlloc(PyTypeObject*type,Py_ssize_tnitems){//......if(PyType_IS_GC(type))obj=_PyObject_GC_Malloc(size);elseobj=(PyObject*)PyObject_MALLOC(size);//.....}staticPyObject*_PyObject_GC_Alloc(intuse_calloc,size_tbasicsize){PyObject*op;PyGC_Head*g;size_t尺寸;如果(basicsize>PY_SSIZE_T_MAX-sizeof(PyGC_Head))返回PyErr_NoMemory();size=sizeof(PyGC_Head)+basicsize;//...}可以看出gc对象内存模型如下:——————|gchead||-------||对象|||————---//使用PyGC_Head将gc对象组成链表typedefunion_gc_head{struct{union_gc_head*gc_next;union_gc_head*gc_prev;Py_ssize_tgc_refs;//gc对象的状态}gc;双假人;/*强制最坏情况对齐*/}PyGC_Head;/设置为取消跟踪未跟踪g->gc.gc_refs=0;_PyGCHead_SET_REFS(g,GC_UNTRACKED);//将新对象统计在第0代_PyRuntime.gc.generations[0].count++;/*分配的GC对象数*///如果第0代对象数超过阈值,触发gcif(_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;}Pythongc也是分代的,也有新生代和老年代的表现。与jvmgcheapgeneration不同的是,这里的generation只是有统计意义的Memoryusage注意,经过对象内存分配的过程,对象实际上并没有分配到某一代。这一步会在PyObject_GC_Alloc中分配内存后执行//加入对象列表,将对象添加到第0代对象列表#define_PyObject_GC_TRACK(o)do{\PyGC_Head*g=_Py_AS_GC(o);\如果(_PyGCHead_REFS(g)!=_PyGC_REFS_UNTRACKED)\Py_FatalError("GC对象已经被跟踪");\_PyGCHead_SET_REFS(g,_PyGC_REFS_REACHABLE);\g->gc.gc_next=_PyGC_generation0;\g->gc.gc_prev=_PyGC_generation0->gc.gc_prev;->gc.gc_next=g;\_PyGC_generation0->gc.gc_prev=g;\}而(0);在GC过程中过滤掉垃圾对象最直接的方法就是标记所有从rootobject开始的可访问对象,其余为垃圾对象。这个过程需要做两件事,1.判断哪些是rootobjects2.从rootobject开始遍历对象。确定GC范围staticPy_ssize_tcollect_generations(void){inti;Py_ssize_tn=0;//找到超过阈值的最老一代(i=NUM??_GENERATIONS-1;i>=0;i--){if(_PyRuntime.gc.generations[i].count>_PyRuntime.gc.generations[i].threshold){//如果long_lived对象不多,避免fullgcif(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;}}返回n;}对象遍历在确定根对象之前,我们需要先解决对象遍历的问题。因为我们需要从一个对象中访问它所引用的对象,也就是广度优先遍历,所以我们不能直接遍历gc对象列表,而是使用了额外的机制。staticvoidsubtract_refs(PyGC_Head*containers){traverseproc遍历;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处理。这样就完成了对象的广度优先遍历。staticintperson_traverse(PyObject*self,visitprocvisit,void*arg){Person*p=(Person*)self;//访问(p->dict,arg)Py_VISIT(p->dict);return0;}OK所有对象和rootobject的直接引用组成一个有向图。先去掉对象间的引用,那么最后一个count>0说明对象中存在非对象间引用,即rootobject连接回上面的例子。使用visit_decrefFunctionstaticintvisit_decref(PyObject*op,void*data){if(PyObject_IS_GC(op)){PyGC_Head*gc=AS_GC(op);如果(_PyGCHead_REFS(gc)>0)_PyGCHead_DECREF(gc);}return0;}第一轮筛选后,将count>0标记为不可达,将count==0标记为不可达staticvoidmove_unreachable(PyGC_Head*young,PyGC_Head*unreachable){PyGC_Head*gc=young->gc.gc_next;while(gc!=young){PyGC_Head*next;//refs>0通过上面的refs-1根对象refs>0if(_PyGCHead_REFS(gc)){PyObject*op=FROM_GC(gc);traverseproctraverse=Py_TYPE(op)->tp_traverse;assert(_PyGCHead_REFS(gc)>0);//设置对象可达_PyGCHead_SET_REFS(gc,GC_REACHABLE);//从这个rootobject访问的对象是reachable(void)traverse(op,(visitproc)visit_reachable,(void*)young);next=gc->gc.gc_next;如果(PyTuple_CheckExact(op)){_PyTuple_MaybeUntrack(op);}}else{//遍历误判时会纠正unreachable。next=gc->gc.gc_next;gc_list_move(gc,无法访问);_PyGCHead_SET_REFS(gc,GC_TENTATIVELY_UNREACHABLE);}gc=下一步;}}staticintvisit_reachable(PyObject*op,PyGC_Head*reachable){if(PyObject_IS_GC(op)){PyGC_Head*gc=AS_GC(op);constPy_ssize_tgc_refs=_PyGCHead_REFS(gc);if(gc_refs==0){//计数+1这样遍历后就归类为可达_PyGCHead_SET_REFS(gc,1);}elseif(gc_refs==GC_TENTATIVELY_UNREACHABLE){//上面遍历时判断错误,将对象放回可达链表gc_list_move(gc,可达);_PyGCHead_SET_REFS(gc,1);}}return0;}存活对象迁移完成可达对象和不可达对象的过滤后,需要将存活对象迁移到老年代if(young!=old){//如果是gen[1],则存活数将影响完整的gcif(generation==NUM??_GENERATIONS-2){_PyRuntime.gc.long_lived_pending+=gc_list_size(young);}//存活的对象进入oldergengc_list_merge(young,old);}else{//如果是fullgc,则untrackdict对象,减轻gc负担untrack_dicts(young);_PyRuntime.gc.long_lived_pending=0;//对象进入长寿状态_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会将这部分对象存放在垃圾列表中。gc_list_init(&finalizers);//将实现tp_del的对象移动到finalizers列表move_legacy_finalizers(&unreachable,&finalizers);//设置为可达move_legacy_finalizer_reachable(&finalizers);//存入垃圾列表,让程序员处理handle_legacy_finalizers(&finalizers),老的);对象清理staticvoiddelete_garbage(PyGC_Head*collectable,PyGC_Head*old){inquiryclear;while(!gc_list_is_empty(collectable)){PyGC_Head*gc=collectable->gc.gc_next;PyObject*op=FROM_GC(gc);//定义的DEBUG_SAVEALL不会被清除,而是存储在抓取列表中}else{if((clear=Py_TYPE(op)->tp_clear)!=NULL){//调用tp_clearPy_INCREF(op);清除(操作);Py_DECREF(操作);tp_clear需要做的是引用对象计数-1,将对象从不可达中移除,并将对象内存释放回内存池staticintperson_clear(PyObject*self){Person*p=(Person*)self;Py_CLEAR(p->听写);//PyObject_GC_DelPy_TYPE(self)->tp_free(self);返回0;}