背景:
为了较好的学习与理解python的内存管理,先从设计框架了解,然后再拆分为小的知识块,针对知识块再逐步理解。说道内存管理,应该说一则是内存的创建分配管理,二则为内存的回收,即垃圾回收。
垃圾回收
如思维导图:
分代回收
gc.set_threshold(threshold0[, threshold1[, threshold2]])
内存创建与管理
C源码地址github.com
小空间内存池
为了避免大量的执行malloc和free的操作,即避免操作系统频繁的再用户态和内核态之间进行切换。因此python引入内存池机制,用于管理小内存块的申请与释放,小于512字节的由内存池管理,超过的由标准的C分配器处理。整个管理小内存的内存池可以视为一个抽象的层次结构,由block pool arean组成。首先我们来看最小的结构block。
block(块)
块 是一定大小的内存块。每个块只能保留一个固定大小的Python对象。块的大小可以从8字节到512字节不等,为了在当前主流的32位和64位平台上获得最佳的性能,必须是8字节的倍数(即与8字节对齐)。为了方便起见,这些块被分为64个尺寸类别。
# obmalloc.c文件里定义如下
#define ALIGNMENT 8 /* must be 2^N */
#define ALIGNMENT_SHIFT 3
#define SMALL_REQUEST_THRESHOLD 512
#define NB_SMALL_SIZE_CLASSES (SMALL_REQUEST_THRESHOLD / ALIGNMENT)
当申请的内存小于512字节,python可以使用不同的级别的block满足对内存的需求,当申请的内存大小超过这个限制时,python会将内存的申请请求转发给第一层的内存管理机制,即PyMem_API来处理了。根据SMALL_REQUEST_THRESHOLD
和ALIGNMENT
, 可以由此得到不同种类的block的size类别。明细表如下:
* For small requests we have the following table:
*
* Request in bytes Size of allocated block Size class idx
* ----------------------------------------------------------------
* 1-8 8 0
* 9-16 16 1
* 17-24 24 2
* 25-32 32 3
* 33-40 40 4
* 41-48 48 5
* 49-56 56 6
* 57-64 64 7
* 65-72 72 8
* ... ... ...
* 497-504 504 62
* 505-512 512 63
*
* 0, SMALL_REQUEST_THRESHOLD + 1 and up: routed to the underlying
* allocator.
*/
举例说明,当申请内存大小为35字节的内存时,实际上PyObject_Malloc
从内存池中划分的内存是40字节的一个block, 从 size class index 为4的pool中划出.转换公式:
# size class index 转换到size class
/* Return the number of bytes in size class I, as a uint. */
#define INDEX2SIZE(I) (((uint)(I) + 1) << ALIGNMENT_SHIFT)
// size class 转换到size class index
size = (uint)(nbytes - 1) >> ALIGNMENT_SHIFT;
block只是概念,并不是实体,是具有一定大小的内存,为了管理block,有了pool的概念。
pool(池)
大小相同的块的集合称为池。通常,池的大小等于系统内存页的大小,即4KB。将池限制为固定大小的块有助于碎片化。如果一个对象被破坏,内存管理器可以用一个相同大小的新对象填充这个空间.池对应的结构为:
/* When you say memory, my mind reasons in terms of (pointers to) blocks */
typedef uint8_t block;
/* Pool for small blocks. */
struct pool_header {
union { block *_padding;
uint count; } ref; /* number of allocated blocks 已分配的block数量 */
block *freeblock; /* pool's free list head 链表结构,指向下一个可用的block */
struct pool_header *nextpool; /* next pool of this size class 双链表,下一个池 */
struct pool_header *prevpool; /* previous pool 指向上一个池 "" */
uint arenaindex; /* index into arenas of base adr 创建池时对应的arean地址 */
uint szidx; /* block size class index 对应的block的size class index */
uint nextoffset; /* bytes to virgin block 下一个可用的block偏移量 */
uint maxnextoffset; /* largest valid nextoffset 最后一个block距离开始位置的距离 */
};
typedef struct pool_header *poolp;
pool在运行的任何时刻, 总处于三个状态中的一种:
- used状态: 部分block使用, 不是full也不是empty;
- full: pool中所有的block都被使用,;
- empty: pool中所有的block都未被使用。
针对used状态,为了有效地管理池,Python使用了一个名为usedpools的附加数组。它存储指向按类分组的池的指针。由于相同块大小的所有池都链接在一起。要遍历它们,我们只需要知道列表的开头。如果没有这样大的池,那么将在第一个内存请求时创建一个新池。如图:
arena
pool的集合,每个arena 的大小都有一个默认的值256KB,一个arena中容纳的pool的个数就是ARENA_SIZE / POOL_SIZE = 64
个,arena结构如下:
[obmalloc.c],
#define ARENA_SIZE (256 << 10) /* 256KB */
# 结构
struct arena_object {
uintptr_t address;
block* pool_address;
uint nfreepools;
uint ntotalpools;
struct pool_header* freepools;
struct arena_object* nextarena;
struct arena_object* prevarena;
};
如图:
arena是用来管理一组pool的集合的, arena_object的作用看上去和pool_header的作用是一样的, 但还是有一点差别的, pool_header与其管理的pool是一块连续的内存; 而arena_object与其管理的内存则是分离的,如图:
当pool_header申请时, 它所管理的block集合的内存一定也被申请了; 但是当申请arena_object时, 它所管理的pool集合的内存则没有被申请, 换句话说, arena_object和pool集合还需要建立关系。
当一个arena的arena_object没有与pool集合建立联系时, 这时的arena处于"未使用"
状态; 建立关系后, arena就转为"可用"
状态. 对每一个状态, 都有一个 arena 的链表. "未使用" 的arena的链表表头是unused_arena_objects
. 此时的arena与arena之间通过nextarena
连接, 是一个单向链表; 而 "可用" 的arena的表头是usable_arenas
, arena 与 arena 通过nextarena
和prevarena
连接, 是一个双向链表, 如图展示了多个arena的可能状态。
这里一定要注意有两个链表,unused_arena_objects属于单链表,usable_arenas属于双链表。
在程序运行时,当需要申请内存时,首先会检查unused_arena_objects
链表中是否还有"未使用"
的arena。 如果unused_arena_objects
中存在未使用的arena, 那么直接从中取出一个arena, 调整unused_arena_objects指针, 与抽取的arena断绝联系. python申请一块大小为ARENA_SIZE
(256KB)的内存, 将申请的内存地址复制给arena的address.这个地址就是维护pool的集合用的. 这块256KB的内存就是pool的容身之处, arena_object和pool集合建立了联系, 具备了 "可用" 的条件, 加入了usable_arenas
链表 。源码如下:
/* Allocate a new arena. If we run out of memory, return NULL. Else
* allocate a new arena, and return the address of an arena_object
* describing the new arena. It's expected that the caller will set
* `usable_arenas` to the return value.
*/
static struct arena_object*
new_arena(void)
{
struct arena_object* arenaobj;
uint excess; /* number of bytes above pool alignment */
void *address;
static int debug_stats = -1;
if (debug_stats == -1) {
char *opt = Py_GETENV("PYTHONMALLOCSTATS");
debug_stats = (opt != NULL && *opt != '0');
}
if (debug_stats)
_PyObject_DebugMallocStats(stderr);
if (unused_arena_objects == NULL) {
uint i;
uint numarenas;
size_t nbytes;
/* Double the number of arena objects on each allocation.
* Note that it's possible for `numarenas` to overflow.
*/
numarenas = maxarenas ? maxarenas << 1 : INITIAL_ARENA_OBJECTS;
if (numarenas <= maxarenas)
return NULL; /* overflow */
#if SIZEOF_SIZE_T <= SIZEOF_INT
if (numarenas > SIZE_MAX / sizeof(*arenas))
return NULL; /* overflow */
#endif
nbytes = numarenas * sizeof(*arenas);
arenaobj = (struct arena_object *)PyMem_RawRealloc(arenas, nbytes);
if (arenaobj == NULL)
return NULL;
arenas = arenaobj;
/* We might need to fix pointers that were copied. However,
* new_arena only gets called when all the pages in the
* previous arenas are full. Thus, there are *no* pointers
* into the old array. Thus, we don't have to worry about
* invalid pointers. Just to be sure, some asserts:
*/
assert(usable_arenas == NULL);
assert(unused_arena_objects == NULL);
/* Put the new arenas on the unused_arena_objects list. */
for (i = maxarenas; i < numarenas; ++i) {
arenas[i].address = 0; /* mark as unassociated */
arenas[i].nextarena = i < numarenas - 1 ?
&arenas[i+1] : NULL;
}
/* Update globals. */
unused_arena_objects = &arenas[maxarenas];
maxarenas = numarenas;
}
/* Take the next available arena object off the head of the list. */
assert(unused_arena_objects != NULL);
arenaobj = unused_arena_objects;
unused_arena_objects = arenaobj->nextarena;
assert(arenaobj->address == 0);
address = _PyObject_Arena.alloc(_PyObject_Arena.ctx, ARENA_SIZE);
if (address == NULL) {
/* The allocation failed: return NULL after putting the
* arenaobj back.
*/
arenaobj->nextarena = unused_arena_objects;
unused_arena_objects = arenaobj;
return NULL;
}
arenaobj->address = (uintptr_t)address;
++narenas_currently_allocated;
++ntimes_arena_allocated;
if (narenas_currently_allocated > narenas_highwater)
narenas_highwater = narenas_currently_allocated;
arenaobj->freepools = NULL;
/* pool_address <- first pool-aligned address in the arena
nfreepools <- number of whole pools that fit after alignment */
arenaobj->pool_address = (block*)arenaobj->address;
arenaobj->nfreepools = ARENA_SIZE / POOL_SIZE;
assert(POOL_SIZE * arenaobj->nfreepools == ARENA_SIZE);
excess = (uint)(arenaobj->address & POOL_SIZE_MASK);
if (excess != 0) {
--arenaobj->nfreepools;
arenaobj->pool_address += POOL_SIZE - excess;
}
arenaobj->ntotalpools = arenaobj->nfreepools;
return arenaobj;
}
总结
- block会被垃圾回收吗,垃圾回收针对的对象是什么?
- pool 的状态
- arena的维护
- 垃圾回收中的 引用计算 分代回收 标记-清除