對象內存管理
python中對於對象內存管理有兩種方法,引用計數/GC 。
引用計數策略應用到每個對象的管理中,接收/返回對象都需要+-對象的計數,而對象是否支持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對象內存分配過程:
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遍歷對象。
確定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;
}