之前介绍了Python垃圾回收的简介,它简要介绍了三种垃圾回收器:引用计数回收器,标记清除回收器和世代回收器,这里会给出Python中基础类型在C语言中的内存模型,以帮助我们理解下一节三种垃圾回收器结合使用流程。
首先介绍一下Python对象的内存模型,如下图1所示:
上图可以看到,一个PyObject必须包含ob_refcnt
和ob_type
。ob_refcnt
是这个对象的引用计数,而ob_type
则是指向_typeobject
结构体的指针,它是Python内部的一种特殊对象,它是用来制定一个对象类型的类型对象,所以上图中它指向了一个PyTypeObject
。
在PyTypeObject中定义了大量的函数指针,这些函数指针最终都会指向某个函数,或者指向NULL。这些函数指针可以视为类型对象中所定义的操作,而这些操作直接决定着一个对象在运行时所表现出的行为。
在Python中,对象机制的核心其实非常简单,一个是引用计数,一个是类型信息。
我们再来看定长对象None
, Int
,List
,其结构如下图2所示:
None
,
Int
,
List
对象内存模型
上图可以看到,None对象的内存模型是与PyObject一致的;而Int对象则是多了一个ob_ival
,这个字段其实就是存储Int真实的value。
问题一:令人奇怪的是,在Python中,List其实是一个可变长的,类似于C++中的vector的数据结构,为什么它的内存模型是定长的呢?
其实它的内存模型的字段都是元数据(meta-data),而真正存放数组数据的是ob_item
指向的一个数组。
问题二:在PyListObject
对象中,有一个ob_size
,而在最后为什么又有一个allocated
,那么这两个变量之间的关系是什么呢?
其实,ob_size
和allocated
都和PyListObject
对象的内存管理有关,PyListObject
所采用的内存管理策略和C++中的vector采取内存管理策略是一样的。在每一次需要申请内存的时候, PyListObject
总会申请一大块内存,这是申请的总内存的大小记录在allocated
中,而实际被使用了的内存的数量则记录在了ob_size
中。
我们再来看看变长对象的内存模型,例如tuple
,它的结构如下图3所示:
tuple
对象内存模型
上图就可以解释,为什么tuple在python中使用是一个定长的list,为什么在PyObject中却是变长对象了。
而string的内存模型如下图所示:
string
对象内存模型
在创建PyStringObject
对象时,除了为PyString_Object
申请内存,还有为字符数组内的元素申请额外的内存(绿色填充的字符数组内存)。然后将hash缓存值设为-1,将参数str指向的字符数组内的字符拷贝到PyStringObject
所维护的空间中,在拷贝过程中,将字符数组最后的'\0'
字符也拷贝了。
上面讲述了很多python的基础数据类型,而这些基础类型(包括PyTypeObject
)都是对象(All is Object)。每个对象至少包含了ob_refcnt
和ob_type
两个字段,如果是32bit的操作系统,那么他们共是8个字节。
本节主要罗列在Python基础类型在c语言中的内存模型,也是深入理解Python 垃圾回收机制的预备知识。欢迎关注csdz的github