PHP - 内存管理

来源:互联网 发布:淘宝手机店3c认证 编辑:程序博客网 时间:2024/04/29 17:43

前面几篇文章主要介绍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_bucketslarge_free_bucketsrest_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)由于小内存块指针数组只会使用prevnext节点,所以为了节省内存,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)->_mallocalloc_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.sizeZEND_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_bucketsheap->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_listmm_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_bucketslarge_free_bucketsrest_buckets指针数组分别管理小空闲内存块大空闲内存块超大内存块指针。

0 0
原创粉丝点击