前面几篇文章主要介绍PHP-FPM,沿着”FPM运行原理->运行模式->进程管理->定时事件”方向,我们对FPM已经有了一个较为深刻的认识。今天这篇文章将介绍PHP的另一核心功能,内存管理。
很多开源软件都有一套自己维护的内存管理体系,例如,nginx。php作为世界上“最好”的语言,当然也不例外。PHP内存管理功能,简单来说,就是申请一块大的内存来管理自己的内存结构;代码非常之精巧,其使用“内存对齐”原则,从而可以减少cpu读取次数。
php的内存管理源码位于zend_alloc.h/zend_alloc.c,内部定义一个zend_heap内存管理结构体。
struct _zend_mm_heap {
int use_zend_alloc; //是否使用zend内存管理
void *(*_malloc)(size_t);//申请
void (*_free)(void*);//释放
void *(*_realloc)(void*, size_t); //重新分配内存
size_t free_bitmap; //小内存块bit位图
size_t large_free_bitmap;//大内存块bit位图
size_t block_size;//block大小
size_t compact_size;
zend_mm_segment *segments_list; //segments内存链表
zend_mm_storage *storage;
size_t real_size; //真实大小
size_t real_peak; //真实峰值
size_t limit;//内存限制
size_t size;//申请大小
size_t peak;//申请大小峰值
size_t reserve_size;
void *reserve;
int overflow;
int internal;
#if ZEND_MM_CACHE
unsigned int cached;
zend_mm_free_block *cache[ZEND_MM_NUM_BUCKETS];
#endif
zend_mm_free_block *free_buckets[ZEND_MM_NUM_BUCKETS*2];//小内存块指针
zend_mm_free_block *large_free_buckets[ZEND_MM_NUM_BUCKETS];//大内存块指针
zend_mm_free_block *rest_buckets[2];//rest内存块指针
#if ZEND_MM_CACHE_STAT
struct {
int count;
int max_count;
int hit;
int miss;
} cache_stat[ZEND_MM_NUM_BUCKETS+1];
#endif
};
php的内存分为small_free_block
(小内存块)、large_free_block
(大内存块)、rest_block
(超大内存块)三种,分别使用free_buckets
、large_free_buckets
、rest_buckets
指针对象进行管理。
内存块 | 32位 | 64位 |
---|---|---|
small_free_block | X<272 | X<544 |
large_free_block | 272< X <=524288 | 544< X <=524224 |
rest_block | X>524288 | X>524224 |
一、初始化内存管理
内存管理的初始化流程:zend_startup
->start_memory_manager
->alloc_globals_ctor
->zend_mm_startup
。在FPM运行环境下,zend_startup
函数在FPM启动时进行调用。
zend_mm_startup
函数的功能分为两块:选择内存分配方案 和 初始化内存管理对象。
static const zend_mm_mem_handlers mem_handlers[] = {
#ifdef HAVE_MEM_WIN32
ZEND_MM_MEM_WIN32_DSC,
#endif
#ifdef HAVE_MEM_MALLOC
ZEND_MM_MEM_MALLOC_DSC,
#endif
#ifdef HAVE_MEM_MMAP_ANON
ZEND_MM_MEM_MMAP_ANON_DSC,
#endif
#ifdef HAVE_MEM_MMAP_ZERO
ZEND_MM_MEM_MMAP_ZERO_DSC,
#endif
{NULL, NULL, NULL, NULL, NULL, NULL}
};
ZEND_API zend_mm_heap *zend_mm_startup(void)
{
//...省略部分代码...
handlers = &mem_handlers[i];
//...省略部分代码...
heap = zend_mm_startup_ex(handlers, seg_size, ZEND_MM_RESERVE_SIZE, 0, NULL);
//...省略部分代码...
return heap;
}
为了兼容多种系统运行环境,PHP实现了4种内存分配方案:ZEND_MM_MEM_WIN32_DSC、ZEND_MM_MEM_MALLOC_DSC、ZEND_MM_MEM_MMAP_ANON_DSC、ZEND_MM_MEM_MMAP_ZERO_DSC,默认使用mem_handlers[0]
,当然我们可以通过修改ZEND_MM_MEM_TYPE
系统配置进行调整。
zend_mm_startup_ex
负责内存管理对象的初始化操作。
ZEND_API zend_mm_heap *zend_mm_startup_ex(const zend_mm_mem_handlers *handlers, size_t block_size, size_t reserve_size, int internal, void *params)
{
//...省略部分代码...
//初始化storage
storage = handlers->init(params);
//...省略部分代码...
//设置handlers类库
storage->handlers = handlers;
heap = malloc(sizeof(struct _zend_mm_heap));
//...省略部分代码...
heap->storage = storage;
//内存块大小
heap->block_size = block_size;
heap->compact_size = 0;
heap->segments_list = NULL;
//初始化heap
zend_mm_init(heap);
//...省略部分代码...
heap->use_zend_alloc = 1;
//...省略部分代码...
return heap;
}
- 首先,调用handlers->init(params);初始化
storage
对象管理分配内存API(handlers),以ZEND_MM_MEM_MALLOC_DSC
为例,调用的是zend_mm_mem_dummy_init
方法。
typedef struct _zend_mm_storage zend_mm_storage;
struct _zend_mm_storage {
const zend_mm_mem_handlers *handlers;
void *data;
};
static zend_mm_storage* zend_mm_mem_dummy_init(void *params)
{
return malloc(sizeof(zend_mm_storage));
}
- 然后,为
heap
分配内存,并调用zend_mm_init
初始化。
static inline void zend_mm_init(zend_mm_heap *heap)
{
//...省略部分代码...
p = ZEND_MM_SMALL_FREE_BUCKET(heap, 0);
for (i = 0; i < ZEND_MM_NUM_BUCKETS; i++) {
p->next_free_block = p;
p->prev_free_block = p;
p = (zend_mm_free_block*)((char*)p + sizeof(zend_mm_free_block*) * 2);
heap->large_free_buckets[i] = NULL;
}
heap->rest_buckets[0] = heap->rest_buckets[1] = ZEND_MM_REST_BUCKET(heap);
}
此时,系统分别初始化free_buckets
(小内存指针数组)、large_free_buckets
(大内存指针数组)、rest_buckets
(超大指针数组)。这里有一个比较难理解的地方:ZEND_MM_SMALL_FREE_BUCKET
函数。
typedef struct _zend_mm_free_block {
zend_mm_block_info info;
struct _zend_mm_free_block *prev_free_block;
struct _zend_mm_free_block *next_free_block;
struct _zend_mm_free_block **parent;
struct _zend_mm_free_block *child[2];
} zend_mm_free_block;
typedef struct _zend_mm_small_free_block {
zend_mm_block_info info;
struct _zend_mm_free_block *prev_free_block;
struct _zend_mm_free_block *next_free_block;
} zend_mm_small_free_block;
#define ZEND_MM_SMALL_FREE_BUCKET(heap, index) \
(zend_mm_free_block*) ((char*)&heap->free_buckets[index * 2] + \
sizeof(zend_mm_free_block*) * 2 - \
sizeof(zend_mm_small_free_block))
从上面代码可以发现,sizeof(zend_mm_free_block*) * 2 - sizeof(zend_mm_small_free_block)) = -sizeof(zend_mm_block_info)。(char*)&heap->free_buckets[index * 2]
获取heap->free_buckets[index * 2]的空间地址。
那么ZEND_MM_SMALL_FREE_BUCKET(heap, 0)
返回的是heap->free_buckets[0]地址往左移动sizeof(zend_mm_block_info)的zend_mm_free_block对象指针。
因此,可以得出heap->free_buckets[0]=p->prev_free_block,heap->free_buckets[1]=p->next_free_block.
之后,p每次循环都偏移sizeof(zend_mm_free_block*) * 2)
。由于小内存块指针数组只会使用prev
和next
节点,所以为了节省内存,PHP分配的大小并不是ZEND_MM_NUM_BUCKETS*sizeof(zend_mm_free_block*),而是ZEND_MM_NUM_BUCKETS*(sizeof(*prev_free_block)+sizeof(*next_free_block))。
下面为heap->free_buckets
结构图。
二、申请内存
php内部通过使用emalloc
申请内存,实际上,调用了_emalloc
函数。
ZEND_API void *_emalloc(size_t size ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC)
{
if (UNEXPECTED(!AG(mm_heap)->use_zend_alloc)) {
return AG(mm_heap)->_malloc(size);
}
return _zend_mm_alloc_int(AG(mm_heap), size ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC);
}
当AG(mm_heap)->use_zend_alloc=0时,则使用AG(mm_heap)->_malloc(size)
申请内存,AG(mm_heap)->_malloc
在alloc_globals_ctor()
被定义为malloc
函数。
当AG(mm_heap)->use_zend_alloc=1时,则使用_zend_mm_alloc_int()
申请内存。
AG(mm_heap)->use_zend_alloc
初始化时,被赋值为1,那么此处调用的是_zend_mm_alloc_int()
。
以下是_zend_mm_alloc_int
的流程图:
简单说明_zend_mm_alloc_int
分配内存的步骤:
1. 判断申请的内存是否为小内存块,若不是,则执行第2步。若是,则从空闲的小内存块中寻找最适合的内存; 如果找到,则跳到第5步。
2. 通过zend_mm_search_large_block
从空闲的大内存块中查找最合适的内存块。若找到,则跳到第5步。
3. 当内存不足的情况下,从heap->rest_buckets
内存中搜索,如果找到,则跳到第5步。
4. 倘若以上3种情况都未找到,则调用ZEND_MM_STORAGE_ALLOC
向系统重新申请一个zend_mm_segment
结构内存块,然后执行第6步。
5. 因分配内存后该内存块空间发生变化,需重新计算保存的位置。SO,执行zend_mm_remove_from_free_list
将该内存块从原列表移除。
6. 计算找到的空闲内存块分配后的大小,调用zend_mm_add_to_free_list
/zend_mm_add_to_rest_list
重新添加到链表。
下面依次按照“初始化zend_mm_segment内存结构->存储空闲内存块->查找空闲内存块->移除空闲内存块”的流程进行介绍。
(一).初始化zend_mm_segment内存结构
//申请一块内存
segment = (zend_mm_segment *) ZEND_MM_STORAGE_ALLOC(segment_size);
if (!segment) {
//...省略部分代码...
return NULL;
}
//emalloc分配的内存大小
heap->real_size += segment_size;
if (heap->real_size > heap->real_peak) {
heap->real_peak = heap->real_size;
}
//设置大小
segment->size = segment_size;
//将新元素放置表头
segment->next_segment = heap->segments_list;
//重置segments_list指向
heap->segments_list = segment;
best_fit = (zend_mm_free_block *) ((char *) segment + ZEND_MM_ALIGNED_SEGMENT_SIZE);
//重置第一个zend_mm_free_block info._prev属性值
ZEND_MM_MARK_FIRST_BLOCK(best_fit);
block_size = segment_size - ZEND_MM_ALIGNED_SEGMENT_SIZE - ZEND_MM_ALIGNED_HEADER_SIZE;
//设置最后一个zend_mm_block info.size
ZEND_MM_LAST_BLOCK(ZEND_MM_BLOCK_AT(best_fit, block_size));
当初始申请内存时,PHP会向系统申请一块block_size大小的内存,然后将申请的内存地址添加至heap->segments_list
表头,该内存块的前ZEND_MM_ALIGNED_SEGMENT_SIZE
字节用于保存segment
结构。
内存从best_fit
指针处开始分配内存,ZEND_MM_MARK_FIRST_BLOCK(best_fit)设置best_fit->info.prev
信息,然后调用ZEND_MM_LAST_BLOCK
设置尾部zend_mm_block
信息。
初始化结构:
分配true_size
后的结构:
(二).存储空闲内存块
当我们向一个空闲内存块申请内存后,如果该空闲内存块剩余的内存可以被再次分配,系统会执行zend_mm_add_to_free_list/zend_mm_add_to_rest_list
将那部分余出内存块添加到相应的空闲内存块指针数组,以便再次利用。
zend_mm_add_to_free_list
:将空闲内存块(即上图的new_free_block
)添加至heap->large_free_buckets
或heap->free_buckets
。
static inline void zend_mm_add_to_free_list(zend_mm_heap *heap, zend_mm_free_block *mm_block)
{
size_t size;
size_t index;
ZEND_MM_SET_MAGIC(mm_block, MEM_BLOCK_FREED);
size = ZEND_MM_FREE_BLOCK_SIZE(mm_block);
if (EXPECTED(!ZEND_MM_SMALL_SIZE(size))) {
zend_mm_free_block **p;
//根据size获取对应的LARGE下标
index = ZEND_MM_LARGE_BUCKET_INDEX(size);
p = &heap->large_free_buckets[index];
mm_block->child[0] = mm_block->child[1] = NULL;
if (!*p) {
*p = mm_block;
mm_block->parent = p;
mm_block->prev_free_block = mm_block->next_free_block = mm_block;
heap->large_free_bitmap |= (ZEND_MM_LONG_CONST(1) << index);
} else {
size_t m;
for (m = size << (ZEND_MM_NUM_BUCKETS - index); ; m <<= 1) {
zend_mm_free_block *prev = *p;
//判断mm_block大小与当前节点大小是否相同
if (ZEND_MM_FREE_BLOCK_SIZE(prev) != size) {
p = &prev->child[(m >> (ZEND_MM_NUM_BUCKETS-1)) & 1];
if (!*p) {
*p = mm_block;
mm_block->parent = p;
mm_block->prev_free_block = mm_block->next_free_block = mm_block;
break;
}
} else {
//如果一致,则维护一个双向链表,分别修改p->prev和p->next及mm_block->next_free_block与mm_block->prev_free_block 指向。
zend_mm_free_block *next = prev->next_free_block;
prev->next_free_block = next->prev_free_block = mm_block;
mm_block->next_free_block = next;
mm_block->prev_free_block = prev;
mm_block->parent = NULL;
break;
}
}
}
} else {
//小块内存
zend_mm_free_block *prev, *next;
//根据size获取对应的下标
index = ZEND_MM_BUCKET_INDEX(size);
//获取index的 prev_free_block位置
prev = ZEND_MM_SMALL_FREE_BUCKET(heap, index);
//index下标的第一个元素
if (prev->prev_free_block == prev) {
//标志位
heap->free_bitmap |= (ZEND_MM_LONG_CONST(1) << index);
}
next = prev->next_free_block;
//设置mm_block指针指向
mm_block->prev_free_block = prev;
mm_block->next_free_block = next;
//next->prev_free_block = mm_block 改变prev->prev_free_block指向
prev->next_free_block = next->prev_free_block = mm_block;
}
}
当mm_block为大内存块时,则执行
if
的语句。
a). 通过ZEND_MM_LARGE_BUCKET_INDEX
计算内存块分布的index.#define ZEND_MM_LARGE_BUCKET_INDEX(S) zend_mm_high_bit(S)
zend_mm_high_bit
得出size
最高二进制位为1的下标。
b). 判断heap->large_free_buckets[index]是否存在元素。(I).若不存在,则将mm_block添加至
heap->large_free_buckets[index]
,设置该元素指向及heap->large_free_bitmap
的二进制index位值。
(Ⅱ).若存在,则按下面流程处理。
[1]. 如果*p节点指向的内存块大小与mm_block
的大小相等时,则将mm_block
节点添加到p
节点维护的双向链表,然后跳出循环。
[2]. 计算mm_block
的size二进制下一个高位值(假设为T),判断p->child[T]是否有元素,若无,则添加到p->child[T],跳出循环。
[3]. m左移一位,继续执行第[1]步。根据上面的流程,我们不难发现,heap->large_free_buckets[index]维护的是一个树状结构,根据二进制位判断添加至child[0]或child[1]节点。对于每个child节点,其内部又是一个双向链表结构。
当mm_block为小内存块时,则执行
else
的语句。a). 通过
ZEND_MM_BUCKET_INDEX
计算出size保存的索引index,这是一个位运算。
b). 判断是否需要设置heap->free_bitmap
的index位值。
c). 将mm_block添加至heap->free_buckets
链表头部。heap->free_buckets
的结构比较容易理解,对于heap->free_buckets[index]
,其内部维护了一个双向链表结构。
zend_mm_add_to_rest_list
: 针对申请内存>heap->block_size
的情况,系统维护了一个heap->rest_buckets
结构来存储超大内存块。
static inline void zend_mm_add_to_rest_list(zend_mm_heap *heap, zend_mm_free_block *mm_block)
{
zend_mm_free_block *prev, *next;
ZEND_MM_SET_MAGIC(mm_block, MEM_BLOCK_FREED);
if (!ZEND_MM_SMALL_SIZE(ZEND_MM_FREE_BLOCK_SIZE(mm_block))) {
mm_block->parent = NULL;
}
prev = heap->rest_buckets[0];
next = prev->next_free_block;
mm_block->prev_free_block = prev;
mm_block->next_free_block = next;
prev->next_free_block = next->prev_free_block = mm_block;
}
上面的代码很容易理解,heap->rest_buckets只有两个指针,内部维护了一个双向链表结构。可以认为是一个简版的heap->free_buckets
结构。
(三).查找空闲内存块
- 对于小内存块,代码如下:
size_t index = ZEND_MM_BUCKET_INDEX(true_size);
size_t bitmap;
bitmap = heap->free_bitmap >> index;
if (bitmap) {
index += zend_mm_low_bit(bitmap);
best_fit = heap->free_buckets[index*2];
goto zend_mm_finished_searching_for_block;
}
首先,计算所需的内存大小对应的二进制位;然后再对heap->free_bitmap
右移index位,如果>0,则表明存在可以容纳true_size
的内存块,再通过调用zend_mm_low_bit(bitmap)
寻找最小的一块内存。
- 对于大内存,则调用zend_mm_search_large_block
。
(四).移除内存块
移除内存块的逻辑代码位于zend_mm_remove_from_free_list
函数。
static inline void zend_mm_remove_from_free_list(zend_mm_heap *heap, zend_mm_free_block *mm_block)
{
//mm_block的prev_free_block和next_free_block指向
zend_mm_free_block *prev = mm_block->prev_free_block;
zend_mm_free_block *next = mm_block->next_free_block;
ZEND_MM_CHECK_MAGIC(mm_block, MEM_BLOCK_FREED);
if (EXPECTED(prev == mm_block)) {
zend_mm_free_block **rp, **cp;
//...省略部分代码...
rp = &mm_block->child[mm_block->child[1] != NULL];
prev = *rp;
if (EXPECTED(prev == NULL)) {
//此时表明,该节点无child[0]、child[1]节点
size_t index = ZEND_MM_LARGE_BUCKET_INDEX(ZEND_MM_FREE_BLOCK_SIZE(mm_block));
ZEND_MM_CHECK_TREE(mm_block);
*mm_block->parent = NULL;
//如果mm_block是最后一个节点,则重置large_free_bitmap标志位
if (mm_block->parent == &heap->large_free_buckets[index]) {
heap->large_free_bitmap &= ~(ZEND_MM_LONG_CONST(1) << index);
}
} else {
//优先遍历child[1],如果没有child[1],则遍历child[0]
while (*(cp = &(prev->child[prev->child[1] != NULL])) != NULL) {
prev = *cp;
rp = cp;
}
*rp = NULL;
subst_block:
ZEND_MM_CHECK_TREE(mm_block);
//改变mm_block->parent指向
*mm_block->parent = prev;
//修改prev->parent指向
prev->parent = mm_block->parent;
//重置prev->child[0]节点
if ((prev->child[0] = mm_block->child[0])) {
ZEND_MM_CHECK_TREE(prev->child[0]);
prev->child[0]->parent = &prev->child[0];
}
if ((prev->child[1] = mm_block->child[1])) {
ZEND_MM_CHECK_TREE(prev->child[1]);
prev->child[1]->parent = &prev->child[1];
}
}
} else {
//...省略部分代码...
//改变指向,移除mm_block
prev->next_free_block = next;
next->prev_free_block = prev;
if (EXPECTED(ZEND_MM_SMALL_SIZE(ZEND_MM_FREE_BLOCK_SIZE(mm_block)))) {
//如果只有index下标只对应一个内存块,则修改free_bitmap标识。
//内存块的大小随着分配后空间会缩小,那么其对应的index可能也会发生变化。
if (EXPECTED(prev == next)) {
size_t index = ZEND_MM_BUCKET_INDEX(ZEND_MM_FREE_BLOCK_SIZE(mm_block));
if (EXPECTED(heap->free_buckets[index*2] == heap->free_buckets[index*2+1])) {
heap->free_bitmap &= ~(ZEND_MM_LONG_CONST(1) << index);
}
}
} else if (UNEXPECTED(mm_block->parent != NULL)) {
goto subst_block;
}
}
}
- 对于小内存块,需修改*mm_block前节点和后节点的指针指向。然后再判断mm_block对应的index是否还有空闲内存块,如果没有,则重置
heap->free_bitmap
对应的index位值。 - 对于大内存块,寻找mm_block下最大的没有子节点的叶子节点,作为一个新的父节点,用于替代
mm_block
。 - 对于超大内存块,只需修改*mm_block前节点和后节点的指针指向。
三、释放内存
php内部通过使用efree
申请内存,实际上,调用了_efree
函数。
ZEND_API void _efree(void *ptr ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC)
{
TSRMLS_FETCH();
if (UNEXPECTED(!AG(mm_heap)->use_zend_alloc)) {
AG(mm_heap)->_free(ptr);
return;
}
_zend_mm_free_int(AG(mm_heap), ptr ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC);
}
从上面的代码可以看出,在AG(mm_heap)->use_zend_alloc=1时,执行了_zend_mm_free_int
函数。
static void _zend_mm_free_int(zend_mm_heap *heap, void *p ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC)
{
zend_mm_block *mm_block;
zend_mm_block *next_block;
size_t size;
if (!ZEND_MM_VALID_PTR(p)) {
return;
}
mm_block = ZEND_MM_HEADER_OF(p);
size = ZEND_MM_BLOCK_SIZE(mm_block);
//...省略部分代码...
heap->size -= size;
next_block = ZEND_MM_BLOCK_AT(mm_block, size);
//如果下一个为空闲节点,则进行合并
if (ZEND_MM_IS_FREE_BLOCK(next_block)) {
zend_mm_remove_from_free_list(heap, (zend_mm_free_block *) next_block);
size += ZEND_MM_FREE_BLOCK_SIZE(next_block);
}
//如果上一个为空闲节点,则进行合并
if (ZEND_MM_PREV_BLOCK_IS_FREE(mm_block)) {
mm_block = ZEND_MM_PREV_BLOCK(mm_block);
zend_mm_remove_from_free_list(heap, (zend_mm_free_block *) mm_block);
size += ZEND_MM_FREE_BLOCK_SIZE(mm_block);
}
//如果该segment内存块都是空闲的内存,则进行回收
//否则,则重新添加到空闲列表中
if (ZEND_MM_IS_FIRST_BLOCK(mm_block) &&
ZEND_MM_IS_GUARD_BLOCK(ZEND_MM_BLOCK_AT(mm_block, size))) {
zend_mm_del_segment(heap, (zend_mm_segment *) ((char *)mm_block - ZEND_MM_ALIGNED_SEGMENT_SIZE));
} else {
ZEND_MM_BLOCK(mm_block, ZEND_MM_FREE_BLOCK, size);
zend_mm_add_to_free_list(heap, (zend_mm_free_block *) mm_block);
}
}
- 判断前一块是否为空闲内存块,如果是,则进行合并。
- 判断后一块是否为空闲内存块,如果是,则进行合并。
- 判断mm_block所属的整个
segment
是否都未使用,如果是,则对整个segment
内存块进行回收,否则,执行zend_mm_add_to_free_list
将mm_block
添加至空闲内存块列表中。
三、回收内存
PHP执行_zend_mm_free_int
释放内存时,其实并不是真正意义的释放内存,只是将内存块的指针进行了一些调整。
当PHP脚本执行完成时,系统会调用php_request_shutdown
进行回收内存。
执行流程:php_request_shutdown
->shutdown_memory_manager
->zend_mm_shutdown
。
zend_mm_shutdown
函数的核心代码如下:
storage = heap->storage;
segment = heap->segments_list;
//释放内存
while (segment) {
prev = segment;
segment = segment->next_segment;
ZEND_MM_STORAGE_FREE(prev);
}
if (full_shutdown) {
storage->handlers->dtor(storage);
if (!internal) {
free(heap);
}
} else {
//重置heap->segments_list
if (heap->compact_size &&
heap->real_peak > heap->compact_size) {
storage->handlers->compact(storage);
}
heap->segments_list = NULL;
zend_mm_init(heap);
heap->real_size = 0;
heap->real_peak = 0;
heap->size = 0;
heap->peak = 0;
if (heap->reserve_size) {
heap->reserve = _zend_mm_alloc_int(heap, heap->reserve_size ZEND_FILE_LINE_CC ZEND_FILE_LINE_EMPTY_CC);
}
heap->overflow = 0;
}
heap->segments_list
保存了所有segments
内存块的指针,当需要回收内存时,只需遍历heap->segments_list
,然后调用ZEND_MM_STORAGE_FREE
进行释放。
# define ZEND_MM_STORAGE_FREE(ptr) heap->storage->handlers->_free(heap->storage, ptr)
以ZEND_MM_MEM_MALLOC_DSC
内存分配方案为例,ZEND_MM_STORAGE_FREE
实际上调用的是zend_mm_mem_malloc_free
。
static void zend_mm_mem_malloc_free(zend_mm_storage *storage, zend_mm_segment *ptr)
{
free(ptr);
}
而zend_mm_mem_malloc_free
内部调用C语言free
函数。
释放内存后,判断full_shutdown
值,如果为1,则执行storage->handlers->dtor
。反之,重置heap
对象。
以上就是PHP内存管理的全部内容。简单来说,PHP内存管理,其实就是申请一块大的内存管理自己的内存存储结构。通过调整内存块的指针指向来实现申请和释放。其内部定义了一个zend_mm_heap
内存管理结构。
当PHP向系统申请的一个内存块(
zend_mm_segment
),PHP会将新内存块地址保存至heap->segments_list
链表进行管理。
当PHP内部调用emlloc
申请内存时,系统会从heap->segments_list
剩余的空闲块进行查找。
为了高效、精确地查找内存块,PHP使用free_buckets
、large_free_buckets
及rest_buckets
指针数组分别管理小空闲内存块
、大空闲内存块
、超大内存块
指针。