整本源码剖析的书基本看完, 不过我觉得第二部分有太多不解, 另外第三部分也是精华, 等过段时间会再看一遍, 听说这本书第二版在编辑中, 等书出了一定要第一时间买一本…
#1. Python运行环境初始化 python运行时环境初始化从Py_InitializeEX(pythonrun.c)开始.其中PyInterpreterState是对进程的模拟, PyThreadState是对线程的模拟.
Py_InitializeEX:
调用PyInterpreterState_New创建一个新的PyInterpreterState对象(pystate.c), python运行中interp_head全局管理一个PyInterpreterState对象链表(多进程)
调用PyThreadState_New创建一个新的PyThreadState对象(pystate.c),并关联进程和线程
python运行环境中, _PyThreadState_Current全局变量维护当前活动线程对应的PyThreadState对象
创建进程线程对象后, 通过_PyBuiltin_Init设置系统__builtin__ module(所有类型对象,函数集合, 文档),interp->builtins
设置sys module, interp->sysdict
设置python收缩module的默认搜索路径集合PySys_SetPath(sysmodule.c)
初始化import环境_PyImport_Init()
初始化python内建exceptions
备份exceptions module和内建模块
在sys module中添加一些对象, 用于import机制
python之后将创建__main__ module, 加入到interp->modules(作为主程序运行的源文件可视为__main__), python运行程序沿着名字空间找到__name__就是__main__
site.py将site-package加入到sys.path中
##1.1. 激活虚拟机
Py_InitializeEX成功完成后, 调用PyRun_AnyFileExFlags(main.c)`进入字节码虚拟机, 通过Py_FdIsInteractive判断是否为交互式方式运行, 是则执行PyRun_InteractiveLoopFlags; 不是则执行PyRun_SimpleFileExFlags
交互式运行方法
执行PyRun_InteractiveLoopFlags(pythonrun.c)
创建交互式环境提示符
进入交互式环境
通过PyRun_InteractiveOneFlags函数编译交互式环境输入的python语句(生成抽象语法树), 执行输入的python代码(run_mod)
脚本文件运行方法
执行PyRun_SimpleFileExFlags(pythonrun.c)
在__main__ module中设置__file__属性
通过PyRun_FileExFlags执行脚本文件
编译并执行(run_mod)
##1.2. 启动虚拟机
run_mode开始, python启动字节码虚拟机
基于抽象语法树编译字节码指令序列, 创建PycodeObject对象
创建PyFrameObject对象(运行环境), 执行PyCodeObject中的字节码指令序列
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 static PyObject * run_mod(mod_ty mod, const char *filename, PyObject *globals , PyObject *locals , PyCompilerFlags *flags, PyArena *arena) { PyCodeObject *co; PyObject *v; co = PyAST_Compile(mod, filename, flags, arena); if (co == NULL) return NULL; v = PyEval_EvalCode(co, globals , locals ); Py_DECREF(co); return v; } PyObject * PyEval_EvalCode(PyCodeObject *co, PyObject *globals , PyObject *locals ) { return PyEval_EvalCodeEx(co, globals , locals , (PyObject **)NULL, 0 , (PyObject **)NULL, 0 , (PyObject **)NULL, 0 , NULL); }
python所有的线程共享相同的builtin名字空间
#2. Python模块的动态加载机制 python的一个module从硬盘到内存的加载
1 2 3 4 5 6 7 def show_moudules () : import sys for item in sys.modules.items() : print item if __name__ == '__main__' : show_moudules()
嵌套的import操作并不影响上一层的孔子空间, 只影响各自module自身的名字空间(module自身维护的dict)
Python中,module是由一个单独的文件实现的
package是通过文件夹来实现的(文件夹中要由__init__.py)
动态加载
在加载module时, 如A.B.c, python内部将这个module表示为一个树状结构, python动态加载时, 首先会将树状结构分解, 形成(A, B, c)节点集合, 然后从左到右依次到sys.modules中查找每一个符号所对应的module是否已经能够被加载. 如果被加载了, 则A对应PyModuleObject对象中维护着一个元信息__path__, 表示这个package路径.此时B,c没有加载, 接下来对B的搜索只在A.__path__中进行
其中sys.modules集合作为module pool, 保存module的唯一映像, 某个.py文件通过import声明时,python将在这个pool中寻找, 如存在, 则引入符号到该.py的命名空间中, 并将其关联到该module. 如果不在pool, 这是才进行动态加载
##2.1. from和import
from和import结合, 使用python虚拟街在当前名字空间中引入符号尽可能少, 从而避免名字空间污染
python还提供了符号重命名机制
通过as关键字控制module以什么名字被引入到当前的local名字空间
符号的销毁和重载
通过import加载module会一直占用内存, 通过del在命名空间删除该符号, 通过reload对module进行重载
##2.2. import实现机制
import机制起点是builtin module中的___import__操作(builtin___import__函数)
import机制3个功能:
python运行时的全局module pool的维护和搜索
解析与搜索module路径的树状结构
对不同文件格式的module的动态加载机制
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 static PyObject *builtin___import__ (PyObject *self, PyObject *args, PyObject *kwds) { static char *kwlist[] = {"name" , "globals" , "locals" , "fromlist" , "level" , 0 }; char *name; PyObject *globals = NULL ; PyObject *locals = NULL ; PyObject *fromlist = NULL ; int level = -1 ; if (!PyArg_ParseTupleAndKeywords(args, kwds, "s|OOOi:__import__" , kwlist, &name, &globals, &locals, &fromlist, &level)) return NULL ; return PyImport_ImportModuleLevel(name, globals, locals, fromlist, level); } #import.c PyObject * PyImport_ImportModuleLevel (char *name, PyObject *globals, PyObject *locals, PyObject *fromlist, int level) { PyObject *result; _PyImport_AcquireLock(); result = import_module_level(name, globals, locals, fromlist, level); if (_PyImport_ReleaseLock() < 0 ) { Py_XDECREF(result); PyErr_SetString(PyExc_RuntimeError, "not holding the import lock" ); return NULL ; } return result; }
执行builtin___import__函数完成对参数的拆包(进行import前, 会对import动作加锁, 进入import后, 解锁)
进入PyImport_ImportModuleLevel函数(import.c)执行import_module_level(对树形结构遍历), 获得import动作发生的packge环境, 解析module的路径结构(树), 依次加载每个package/module
在import_module_level中的load_next中调用import_submodule完成搜索module并加载module的工作
对于py文件虚拟机执行load_source_module(编译, 产生PyCodeObject对象执行字节码); 对于pyc, 虚拟机调用load_compiled_module(至少了编译动作); 对于内建module, 只有一部分常用的在运行时被加载到sys.modules中, 对于不常用的进行import操作,, 虚拟机调用init_builtin加载module(在内建module列表中找到后, 调用init初始化); 对于C扩展module, 会调用_PyImport_LoadDynamicModule(查找python的module备份是否有module, 获得module初始化起始地址并调用, 从sys.modules获得已经加载的module并设置__file__属性, 最后加入到python的module备份)
import动作是发生在一个package环境中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 struct _inittab { char *name; void (*initfunc)(void ); }; struct _inittab *PyImport_Inittab = _PyImport_Inittab;struct _inittab _PyImport_Inittab [] = { {"array" , initarray}, {"_ast" , init_ast}, #ifdef MS_WINDOWS #ifndef MS_WINI64 {"audioop" , initaudioop}, #endif #endif {"binascii" , initbinascii}, {"cmath" , initcmath}, {"errno" , initerrno}, {"future_builtins" , initfuture_builtins}, {"gc" , initgc}, #ifndef MS_WINI64 {"imageop" , initimageop}, #endif {"math" , initmath}, {"_md5" , init_md5}, {"nt" , initnt}, {"operator" , initoperator}, {"signal" , initsignal}, {"_sha" , init_sha}, {"_sha256" , init_sha256}, {"_sha512" , init_sha512}, {"strop" , initstrop}, {"time" , inittime}, #ifdef WITH_THREAD {"thread" , initthread}, #endif {"cStringIO" , initcStringIO}, {"cPickle" , initcPickle}, #ifdef WIN32 {"msvcrt" , initmsvcrt}, {"_locale" , init_locale}, #endif {"_subprocess" , init_subprocess}, {"_codecs" , init_codecs}, {"_weakref" , init_weakref}, {"_hotshot" , init_hotshot}, {"_random" , init_random}, {"_bisect" , init_bisect}, {"_heapq" , init_heapq}, {"_lsprof" , init_lsprof}, {"itertools" , inititertools}, {"_collections" , init_collections}, {"_symtable" , init_symtable}, {"mmap" , initmmap}, {"_csv" , init_csv}, {"_sre" , init_sre}, {"parser" , initparser}, {"_winreg" , init_winreg}, {"_struct" , init_struct}, {"datetime" , initdatetime}, {"_functools" , init_functools}, {"_json" , init_json}, {"xxsubtype" , initxxsubtype}, {"zipimport" , initzipimport}, {"zlib" , initzlib}, {"_multibytecodec" , init_multibytecodec}, {"_codecs_cn" , init_codecs_cn}, {"_codecs_hk" , init_codecs_hk}, {"_codecs_iso2022" , init_codecs_iso2022}, {"_codecs_jp" , init_codecs_jp}, {"_codecs_kr" , init_codecs_kr}, {"_codecs_tw" , init_codecs_tw}, {"marshal" , PyMarshal_Init}, {"imp" , initimp}, {"__main__" , NULL }, {"__builtin__" , NULL }, {"sys" , NULL }, {"exceptions" , NULL }, {"_warnings" , _PyWarnings_Init}, {"_io" , init_io}, {0 , 0 } };
#3. python多线程机制
Python通过GIL来互斥不同线程对解释器的使用, python借助底层操作系统提供的线程调度机制来决定下一个进入python解释器的线程.
##3.1. python thread
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 static PyMethodDef thread_methods[] = { {"start_new_thread" , (PyCFunction)thread_PyThread_start_new_thread, METH_VARARGS, start_new_doc}, {"start_new" , (PyCFunction)thread_PyThread_start_new_thread, METH_VARARGS, start_new_doc}, {"allocate_lock" , (PyCFunction)thread_PyThread_allocate_lock, METH_NOARGS, allocate_doc}, {"allocate" , (PyCFunction)thread_PyThread_allocate_lock, METH_NOARGS, allocate_doc}, {"exit_thread" , (PyCFunction)thread_PyThread_exit_thread, METH_NOARGS, exit_doc}, {"exit" , (PyCFunction)thread_PyThread_exit_thread, METH_NOARGS, exit_doc}, {"interrupt_main" , (PyCFunction)thread_PyThread_interrupt_main, METH_NOARGS, interrupt_doc}, {"get_ident" , (PyCFunction)thread_get_ident, METH_NOARGS, get_ident_doc}, {"_count" , (PyCFunction)thread__count, METH_NOARGS, _count_doc}, {"stack_size" , (PyCFunction)thread_stack_size, METH_VARARGS, stack_size_doc}, {NULL , NULL } }; static PyObject *thread_PyThread_start_new_thread (PyObject *self, PyObject *fargs) { PyObject *func, *args, *keyw = NULL ; struct bootstate *boot ; long ident; if (!PyArg_UnpackTuple(fargs, "start_new_thread" , 2 , 3 , &func, &args, &keyw)) return NULL ; if (!PyCallable_Check(func)) { PyErr_SetString(PyExc_TypeError, "first arg must be callable" ); return NULL ; } if (!PyTuple_Check(args)) { PyErr_SetString(PyExc_TypeError, "2nd arg must be a tuple" ); return NULL ; } if (keyw != NULL && !PyDict_Check(keyw)) { PyErr_SetString(PyExc_TypeError, "optional 3rd arg must be a dictionary" ); return NULL ; } boot = PyMem_NEW(struct bootstate, 1 ); if (boot == NULL ) return PyErr_NoMemory(); boot->interp = PyThreadState_GET()->interp; boot->func = func; boot->args = args; boot->keyw = keyw; boot->tstate = _PyThreadState_Prealloc(boot->interp); if (boot->tstate == NULL ) { PyMem_DEL(boot); return PyErr_NoMemory(); } Py_INCREF(func); Py_INCREF(args); Py_XINCREF(keyw); PyEval_InitThreads(); ident = PyThread_start_new_thread(t_bootstrap, (void *) boot); if (ident == -1 ) { PyErr_SetString(ThreadError, "can't start new thread" ); Py_DECREF(func); Py_DECREF(args); Py_XDECREF(keyw); PyThreadState_Clear(boot->tstate); PyMem_DEL(boot); return NULL ; } return PyInt_FromLong(ident); } typedef void *PyThread_type_lock;static PyThread_type_lock interpreter_lock = 0 ; static PyThread_type_lock pending_lock = 0 ; static long main_thread = 0 ;void PyEval_InitThreads (void ) { if (interpreter_lock) return ; interpreter_lock = PyThread_allocate_lock(); PyThread_acquire_lock(interpreter_lock, 1 ); main_thread = PyThread_get_thread_ident(); } typedef struct NRMUTEX { LONG owned ; DWORD thread_id ; HANDLE hevent ; } NRMUTEX, *PNRMUTEX ;
初始化线程环境: PyEval_InitThreads通过PyThread_allocate_lock创建GIL后, 调用任何python C API前, 必须获得GIL(PyThread_acquire_lock), 获得当前主线程的id, 并将其赋值为main_thread
创建线程: 主线程为执行程序时操作系统创建,主线程执行初始化获得GIL 子线程是在PyThread_start_new_thread函数中CreateThread创建出来的(程序中的start_new_thread, 是在主线程中执行的). 子线程创建自身线程状态对象后, 通过_PyGILState_NotrThreadState将这个对象放入线程状态对象链表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 long PyThread_start_new_thread (void (*func)(void *), void *arg) { HANDLE hThread; unsigned threadID; callobj *obj; dprintf(("%ld: PyThread_start_new_thread called\n" , PyThread_get_thread_ident())); if (!initialized) PyThread_init_thread(); obj = (callobj*)HeapAlloc(GetProcessHeap(), 0 , sizeof (*obj)); if (!obj) return -1 ; obj->func = func; obj->arg = arg; #if defined(MS_WINCE) hThread = CreateThread(NULL , Py_SAFE_DOWNCAST(_pythread_stacksize, Py_ssize_t, SIZE_T), bootstrap, obj, 0 , &threadID); #else hThread = (HANDLE)_beginthreadex(0 , Py_SAFE_DOWNCAST(_pythread_stacksize, Py_ssize_t, unsigned int ), bootstrap, obj, 0 , &threadID); #endif if (hThread == 0 ) { #if defined(MS_WINCE) unsigned e = GetLastError(); dprintf(("%ld: PyThread_start_new_thread failed, win32 error code %u\n" , PyThread_get_thread_ident(), e)); #else int e = errno; dprintf(("%ld: PyThread_start_new_thread failed, errno %d\n" , PyThread_get_thread_ident(), e)); #endif threadID = (unsigned )-1 ; HeapFree(GetProcessHeap(), 0 , obj); } else { dprintf(("%ld: PyThread_start_new_thread succeeded: %p\n" , PyThread_get_thread_ident(), (void *)hThread)); CloseHandle(hThread); } return (long ) threadID; }
每个将要从运行态转为等待态的线程都会在被挂起前调用PyThread_release_lock释放GIL(thread_xx.h)并通知所有等待GIL event内核对象的线程
##3.2. python线程的调度
python线程调度机制内建在python解释器的核心PyEval_EvalFrameEx中
主线程先获得GIL, 并执行PyEval_EvalFrameEx函数代码, 这是子线程在t_bootstrap中调用PyEval_AcquireThread, 通过调用PyThread_acquire_lock申请GIL, 但由于GIL被主线程调用, 子线程被挂起. 主线程不断执行字节码, _Py_Ticker不断减一, 当减到0, 将当前维护线程状态置NULL, 然后释放GIL,此时子线程被操作系统的线程调度唤醒, 从而进入PyEval_EvalFrameEx. 对于主线程虽然失去了GIL, 但是没被挂起, 所以可以被再次切换为活动线程, 再次申请GIL, 由于被子线程占有, 主线程将自身挂起.
##3.3. 阻塞调度
线程A通过某些操作(如等待输入), 将自身阻塞, python应将等待GIL的线程B唤醒.
##3.4. python子线程的销毁
主线程销毁必须要销毁python的运行时环境, 子线程的销毁不需要进行这些动作
线程的主框架在t_bootstrap中,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 static void t_bootstrap (void *boot_raw) { struct bootstate *boot = (struct bootstate *) boot_raw; PyThreadState *tstate; PyObject *res; tstate = boot->tstate; tstate->thread_id = PyThread_get_thread_ident(); _PyThreadState_Init(tstate); PyEval_AcquireThread(tstate); nb_threads++; res = PyEval_CallObjectWithKeywords( boot->func, boot->args, boot->keyw); if (res == NULL ) { if (PyErr_ExceptionMatches(PyExc_SystemExit)) PyErr_Clear(); else { PyObject *file; PyObject *exc, *value, *tb; PyErr_Fetch(&exc, &value, &tb); PySys_WriteStderr( "Unhandled exception in thread started by " ); file = PySys_GetObject("stderr" ); if (file) PyFile_WriteObject(boot->func, file, 0 ); else PyObject_Print(boot->func, stderr , 0 ); PySys_WriteStderr("\n" ); PyErr_Restore(exc, value, tb); PyErr_PrintEx(0 ); } } else Py_DECREF(res); Py_DECREF(boot->func); Py_DECREF(boot->args); Py_XDECREF(boot->keyw); PyMem_DEL(boot_raw); nb_threads--; PyThreadState_Clear(tstate); PyThreadState_DeleteCurrent(); PyThread_exit_thread(); }
##3.5. Python线程的用户级互斥和同步
python的线程在GIL控制下, 线程之间, 对python解释器, 对C API的访问都是互斥的.(内核级互斥, 用户不可控)
thread中提供了Lock对象, thread.allocate对应的C函数thread_PyThread_allocate_lock, 创建一个lockobject对象,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 typedef struct { PyObject_HEAD PyThread_type_lock lock_lock; PyObject *in_weakreflist; } lockobject; static PyObject *thread_PyThread_allocate_lock (PyObject *self) { return (PyObject *) newlockobject(); } static lockobject *newlockobject (void ) { lockobject *self; self = PyObject_New(lockobject, &Locktype); if (self == NULL ) return NULL ; self->lock_lock = PyThread_allocate_lock(); self->in_weakreflist = NULL ; if (self->lock_lock == NULL ) { Py_DECREF(self); PyErr_SetString(ThreadError, "can't allocate lock" ); return NULL ; } return self; }
关于对thread module封装的threading, 调用threading.thread.start时, 会在_limbo中记录线程, 通过thread.start_new_thread创建原生线程, 线程过程为__bootstrap. 在bootstrap中, 会在_limo中删除线程记录, 转而将线程记录到_active中, 然后调用run, run结束后, 会调用__stop操作, 会调用其中Condition对象的notifyAll通知等待该对象的线程, 这些线程会通过threading.Thread.join操作注册成为Condition对象的等待线程.
#4. Python内存管理
python所有内存管理有两套实现, 由编译符号PYMALLOC_DEBUG控制, debug模式和正常的内存管理模式
内存管理的抽象层次:
最底层(第0层), 操作系统提供的内存管理接口, 比如C运行时的malloc和free接口, 由操作系统管理和实现, python不能干涉, 其他层由python实现并维护
第1层是python基于第0层操作系统的内存管理接口包装形成的(处理平台相关的内存分配行为, PyMem_API)
第2层更高抽象层次的内存管理接口, 是一组以PyObje_为前缀的函数族(主要提供创建Python对象的接口)
第3层主要是对对象缓冲池机制(GC机制)
##4.1. 小块空间的内存池
小块内存池可以看做4层结构: block, pool, arena, 内存池
block是确定大小的内存块(8字节对齐, 8, 16, 32…512, 内存小大称为size class, 每个size class对应一个size class index, index从0开始)
1 2 3 4 5 6 7 #define ALIGNMENT 8 #define ALIGNMENT_SHIFT 3 #define ALIGNMENT_MASK (ALIGNMENT - 1) #define SMALL_REQUEST_THRESHOLD 512 #define NB_SMALL_SIZE_CLASSES (SMALL_REQUEST_THRESHOLD / ALIGNMENT)
当申请一个28字节内存时, PyObject_Malloc从内存池中分配一个32字节的block, 从size class index为3的pool中分配
pool管理一堆固定大小的内存块, pool大小一般为系统内存页(一般为4K, 包括pool_header头部和block几何占用的内存)
1 2 3 4 5 #define SYSTEM_PAGE_SIZE (4 * 1024) #define SYSTEM_PAGE_SIZE_MASK (SYSTEM_PAGE_SIZE - 1) #define POOL_SIZE SYSTEM_PAGE_SIZE #define POOL_SIZE_MASK SYSTEM_PAGE_SIZE_MASK
每一个pool都和一个size class index关联.
将4K内存转换为pool, pool先指向一块4K内存, 设置pool的size class index, 将index转换为size(index = 3即32字节, index = 0即8字节),跳过pool_header的内存, 进行对齐, 当对其中block进行分配时, 如果某些block内存被释放了, 则通过自由block链表(pool_header中的freeblock)进行管理
arena是多个pool的集合(256KB), 容纳pool的个数是ARENA_SIZE/POOL_SIZE = 64, arena管理的内存是分离的, pool_header自身是连续的内存
1 2 3 4 5 6 7 8 9 10 #define ARENA_SIZE (256 << 10) struct arena_object { ... }
当arena的area_object没和poll集合建立联系, arena处于未使用状态(单链表头unused_arena_objects), 一旦建立联系, arena转换为可用状态(双向链表头usable_arenas), 每个状态有一个arena链表
arena是小块内存池的最上层结构, 所有arena的集合是小块内存池
(小块内存和大块内存分界点是512字节), 小于512会在内存池中申请, 申请内存大于512时, PyObject_Malloc蜕变为molloc行为. python启动后, usedpools这个小块空间内存池并不存在任何可用的内存(不存在可用的pool), python采用延迟分配策略, 当确实开始申请小块内存是, python才建立内存池.
##4.2. 循环引用的垃圾收集
python中大多数对象的生命周期是通过对象的引用计数来管理的. 广义上, 引用计数是一种最直接, 最简单的垃圾收集计数. python引入了标记-清除和分代收集计数来填补循环引用的缺陷.
垃圾收集机制一般分为两个阶段: 垃圾检测和垃圾回收
三色标记模型
寻找根对象的集合, root object是一些全局引用和函数栈中的引用, 这些引用所用的对象不可被删除, root object集合是垃圾检测的起点
从root object集合出发, 沿着root object集合中的每个引用, 如果能到达某对象A, 称A可达, 可达对象不能被删除, 这阶段是垃圾检测阶段
垃圾检测后, 所有对象分为可达和不可达, 可达对象被保留, 不可达对象所占用内存将被回收, 这短短是垃圾回收阶段.
##4.3. Python中的垃圾收集
循环引用总是发生在container之间(内部可持有对其他对象的引用的对象, 如list, dict, class), python垃圾收集只需要检测这些container, container要成为可收集对象, 在PyObject_Head前加入PyGC_Head, 并将可收集container对象连接到python内部维护的可收集对象链表中.
将系统所有的内存块按其存活时间划分为不同集合, 每个集合成为一个代, 垃圾收集的频率随着代的增大而减小. python中的分代收集, 分为三代.
从root object触发前, 将内存链表分为root链表和unreachable链表, 完成可达和不可达的标记, 最后unreachable链表中对象就是垃圾收集对象.