python中對於對象內存管理有兩種方法,引用計數/GC 。
引用計數策略應用到每一個對象的管理中,接收/返回對象都須要+-對象的計數,而對象是否支持GC則是可選的,由於GC的存在是爲了解決引用計數留下的循環引用問題,對於沒有包含其餘對象指針的對象能夠不支持GC。python
引用計數的優點在於簡單,把對象銷燬時間分攤到程序生命週期程序員
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函數完成對象清理jvm
#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機制來彌補函數
python gc使用的策略是標記-清除,根據是否從root object可達判斷一個對象是不是垃圾對象ui
因爲是否支持GC是可選的,因此咱們要主動選擇對象是否支持GC,只須要在typeobject中加入一個標記就好 Py_TPFLAGS_HAVE_GC
debug
gc對象內除了對象自己的數據,還增長了一些gc信息,具體能夠看看gc對象內存分配過程:設計
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 分代不一樣的是 這裏的代只是統計意義不具有內存佔用code
注意到走完對象內存分配的流程,對象其實尚未真正的分配到某一代中
在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);
要篩選出垃圾對象,最直接的方法就是從rootobject開始把全部能訪問到的對象都標記上,剩下的就是垃圾對象了。這個過程須要作兩個事,1.肯定哪些是rootobject 2.從rootobject遍歷對象。
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; }
全部對象和對象直接的引用造成了一個有向圖,先把對象之間的引用去掉,那麼最後計數>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; }