原文:Python的垃圾回收机制(二)之内存模型
1. Python对象内存模型
首先介绍一下Python对象的内存模型,如下图1所示:
![这里写图片描述](https://i-blog.csdnimg.cn/blog_migrate/9c6d26436ee3728b176cd3382d7ebc29.png)
图1. PyObject对象内存模型
上图可以看到,一个PyObject必须包含ob_refcnt
和ob_type这两个属性
。ob_refcnt
是这个对象的引用计数,而ob_type
则是指向_typeobject
结构体的指针,它是Python内部的一种特殊对象,它是用来制定一个对象类型的类型对象,所以上图中它指向了一个PyTypeObject
。
在PyTypeObject中定义了大量的函数指针,这些函数指针最终都会指向某个函数,或者指向NULL。这些函数指针可以视为类型对象中所定义的操作,而这些操作直接决定着一个对象在运行时所表现出的行为。如list类内置的各种魔法方法(list.append(), list.sort()等)。
在Python中,对象机制的核心其实非常简单,一个是引用计数,一个是类型信息。
a). 定长对象(Fixed-sized)
我们再来看定长对象None
, Int
,List
,其结构如下图2所示:
![这里写图片描述](https://i-blog.csdnimg.cn/blog_migrate/635c66e83ff99e369e142b505c73ebe0.png)
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
中。
b). 变长对象(Variable-sized)
我们再来看看变长对象的内存模型,例如tuple
,它的结构如下图3所示:
![这里写图片描述](https://i-blog.csdnimg.cn/blog_migrate/ac0760bf210c272213bdad5eb542449e.png)
图3.
tuple
对象内存模型
上图就可以解释,为什么tuple在python中使用是一个定长的list,为什么在PyObject中却是变长对象了。
而string的内存模型如下图所示:
![这里写图片描述](https://i-blog.csdnimg.cn/blog_migrate/c8c00d839044b4f74c07a8b45848e173.png)
图4.
string
对象内存模型
在创建PyStringObject
对象时,除了为PyString_Object
申请内存,还有为字符数组内的元素申请额外的内存(绿色填充的字符数组内存)。然后将hash缓存值设为-1,将参数str指向的字符数组内的字符拷贝到PyStringObject
所维护的空间中,在拷贝过程中,将字符数组最后的'\0'
字符也拷贝了。
c). All is Object
上面讲述了很多python的基础数据类型,而这些基础类型(包括PyTypeObject
)都是对象(All is Object)。每个对象至少包含了ob_refcnt
和ob_type
两个字段,如果是32bit的操作系统,那么他们共是8个字节。