PHP

PHP7源码系列-内存管理

2019年4月11日

内存分配

内存池

内存池是一种常用的代替系统内存分配的方法
优点:

  1. 速度远比 malloc/free 快,因为减少了系统调用的次数,特别是频繁申请/释放内存块的情况
  2. 避免了频繁申请/释放内存之后,系统的大量内存碎片
  3. 可避免内存泄漏

类比:连接池,线程池,都是复用已有资源,减少申请开销

php的内存池实现

Zend/zend_alloc_sizes.h
Zend/zend_alloc.h
Zend/zend_alloc.c

huge内存(若干个chunk):针对大于2M-4K的分配请求,直接调用mmap分配;


large内存(若干个page):针对小于2M-4K,大于3K的分配请求,在chunk上查找满足条件的若干个连续page;>
> small内存(slot):针对小于3K的分配请求;PHP拿出若干个页切割为8字节大小的内存块,拿出若干个页切割为16字节大小的内存块,24字节,32字节等等,将其组织成若干个空闲链表;每当有分配请求时,只在对应的空闲链表获取一个内存块即可;

struct _zend_mm_heap {
#if ZEND_MM_CUSTOM
    int                use_custom_heap;
#endif
#if ZEND_MM_STORAGE
    zend_mm_storage   *storage;
#endif
#if ZEND_MM_STAT
  // 当前已经使用内存的大小
    size_t             size;                    /* current memory usage */
  // 单次申请的内存的峰值
    size_t             peak;                    /* peak memory usage */
#endif
  // 小内存分配的可用位置链表
    zend_mm_free_slot *free_slot[ZEND_MM_BINS]; /* free lists for small sizes */
#if ZEND_MM_STAT || ZEND_MM_LIMIT
  // php当前申请的内存大小
    size_t             real_size;               /* current size of allocated pages */
#endif
#if ZEND_MM_STAT
  // 申请的内存峰值
    size_t             real_peak;               /* peak size of allocated pages */
#endif
#if ZEND_MM_LIMIT
  // 内存限制大小
    size_t             limit;                   /* memory limit */
  // 超出内存限制标记
    int                overflow;                /* memory overflow flag */
#endif
    // huge 链表
    zend_mm_huge_list *huge_list;               /* list of huge allocated blocks */
    // chunk 链表
    zend_mm_chunk     *main_chunk;
  // 未使用的chunk
    zend_mm_chunk     *cached_chunks;           /* list of unused chunks */
  // 已分配的chunk的数量
    int                chunks_count;            /* number of alocated chunks */
    int                peak_chunks_count;       /* peak number of allocated chunks for current request */
    int                cached_chunks_count;     /* number of cached chunks */
    double             avg_chunks_count;        /* average number of chunks allocated per request */
    int                last_chunks_delete_boundary; /* numer of chunks after last deletion */
    int                last_chunks_delete_count;    /* number of deletion over the last boundary */
#if ZEND_MM_CUSTOM
    union {
        struct {
            void      *(*_malloc)(size_t);
            void       (*_free)(void*);
            void      *(*_realloc)(void*, size_t);
        } std;
        struct {
            void      *(*_malloc)(size_t ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC);
            void       (*_free)(void*  ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC);
            void      *(*_realloc)(void*, size_t  ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC);
        } debug;
    } custom_heap;
#endif
};

# chunk
struct _zend_mm_chunk {
  // 指向heap的指针
    zend_mm_heap      *heap;
  // 链表下元素指针
    zend_mm_chunk     *next;
  // 链表上元素指针
    zend_mm_chunk     *prev;
  // 当前chunk剩余的page数量
    int                free_pages;              /* number of free pages */
  // 剩余可分配的chunk数量
    int                free_tail;               /* number of free pages at the end of chunk */
  // 每申请一个chunk,num + 1
    int                num;
    char               reserve[64 - (sizeof(void*) * 3 + sizeof(int) * 3)];
    zend_mm_heap       heap_slot;               /* used only in main chunk */
  // 标记各page是否已经分配 2m / 4k = 512
    zend_mm_page_map   free_map;                /* 512 bits or 64 bytes */
  // 标记各page的用途
    zend_mm_page_info  map[ZEND_MM_PAGES];      /* 2 KB = 512 * 4 */
};

# slot
struct _zend_mm_free_slot {
    zend_mm_free_slot *next_free_slot;
};
image.png

初始化

## 初始化内存管理器
## php_module_startup() -> zend_startup() -> start_memory_manager() 

ZEND_API void start_memory_manager(void)
{
#ifdef ZTS
    ts_allocate_id(&alloc_globals_id, sizeof(zend_alloc_globals), (ts_allocate_ctor) alloc_globals_ctor, (ts_allocate_dtor) alloc_globals_dtor);
#else
    alloc_globals_ctor(&alloc_globals);
#endif
#ifndef _WIN32
#  if defined(_SC_PAGESIZE)
    REAL_PAGE_SIZE = sysconf(_SC_PAGESIZE);
#  elif defined(_SC_PAGE_SIZE)
    REAL_PAGE_SIZE = sysconf(_SC_PAGE_SIZE);
#  endif
#endif
}

static void alloc_globals_ctor(zend_alloc_globals *alloc_globals)
{
#if ZEND_MM_CUSTOM
    char *tmp = getenv("USE_ZEND_ALLOC");

    if (tmp && !zend_atoi(tmp, 0)) {
        alloc_globals->mm_heap = malloc(sizeof(zend_mm_heap));
        memset(alloc_globals->mm_heap, 0, sizeof(zend_mm_heap));
        alloc_globals->mm_heap->use_custom_heap = ZEND_MM_CUSTOM_HEAP_STD;
        alloc_globals->mm_heap->custom_heap.std._malloc = __zend_malloc;
        alloc_globals->mm_heap->custom_heap.std._free = free;
        alloc_globals->mm_heap->custom_heap.std._realloc = __zend_realloc;
        return;
    }
#endif
#ifdef MAP_HUGETLB
    tmp = getenv("USE_ZEND_ALLOC_HUGE_PAGES");
    if (tmp && zend_atoi(tmp, 0)) {
        zend_mm_use_huge_pages = 1;
    }
#endif
    ZEND_TSRMLS_CACHE_UPDATE();
    alloc_globals->mm_heap = zend_mm_init();
}


static zend_mm_heap *zend_mm_init(void)
{
    zend_mm_chunk *chunk = (zend_mm_chunk*)zend_mm_chunk_alloc_int(ZEND_MM_CHUNK_SIZE, ZEND_MM_CHUNK_SIZE);
    zend_mm_heap *heap;

    if (UNEXPECTED(chunk == NULL)) {
#if ZEND_MM_ERROR
#ifdef _WIN32
        stderr_last_error("Can't initialize heap");
#else
        fprintf(stderr, "\nCan't initialize heap: [%d] %s\n", errno, strerror(errno));
#endif
#endif
        return NULL;
    }
    heap = &chunk->heap_slot;
    chunk->heap = heap;
    chunk->next = chunk;
    chunk->prev = chunk;
    chunk->free_pages = ZEND_MM_PAGES - ZEND_MM_FIRST_PAGE;
    chunk->free_tail = ZEND_MM_FIRST_PAGE;
    chunk->num = 0;
    chunk->free_map[0] = (Z_L(1) << ZEND_MM_FIRST_PAGE) - 1;
    chunk->map[0] = ZEND_MM_LRUN(ZEND_MM_FIRST_PAGE);
    heap->main_chunk = chunk;
    heap->cached_chunks = NULL;
    heap->chunks_count = 1;
    heap->peak_chunks_count = 1;
    heap->cached_chunks_count = 0;
    heap->avg_chunks_count = 1.0;
    heap->last_chunks_delete_boundary = 0;
    heap->last_chunks_delete_count = 0;
#if ZEND_MM_STAT || ZEND_MM_LIMIT
    heap->real_size = ZEND_MM_CHUNK_SIZE;
#endif
#if ZEND_MM_STAT
    heap->real_peak = ZEND_MM_CHUNK_SIZE;
    heap->size = 0;
    heap->peak = 0;
#endif
#if ZEND_MM_LIMIT
    heap->limit = (Z_L(-1) >> Z_L(1));
    heap->overflow = 0;
#endif
#if ZEND_MM_CUSTOM
    heap->use_custom_heap = ZEND_MM_CUSTOM_HEAP_NONE;
#endif
#if ZEND_MM_STORAGE
    heap->storage = NULL;
#endif
    heap->huge_list = NULL;
    return heap;
}

分配

释放

GC

Zend/zend_gc.h
Zend/zend_gc.c

引用计数

问题:循环引用

PHP5.3

GC_ROOT_BUFFER_MAX_ENTRIES 10000
_gc_collect_cycles()


php解决循环引用的方法

  1. 将refcount >0 的元素 加入链表 标记为GC_PURPLE
  2. 深度优先遍历buf链表,并将元素的refcount – 1,标记GC_GREY
  3. 再次遍历,将refcount=0 的元素标记为GC_WHITE即垃圾变量,并将refcount > 0 的refcount+1 标记GC_BLACK
  4. 最后一次遍历,将非white的元素删除
  5. 遍历最后的链表,将元素释放

GC_BLACK 未插入buf
GC_PURPLE 已经插入未处理
GC_> GREY refcount 减一> GC_WHITE 垃圾

typedef struct _zend_gc_globals {
    zend_bool         gc_enabled; // 是否启用gc
    zend_bool         gc_active;  // 是否在垃圾检查过程中
    zend_bool         gc_full;    // 缓存区是否已满

    gc_root_buffer   *buf;   // 启动时分配的用于保存可能垃圾的缓存区
    gc_root_buffer    roots; // 指向buf中最新加入的一个可能垃圾
    gc_root_buffer   *unused;// 指向buf中没有使用的buffer
    gc_root_buffer   *first_unused; // 指向buf中第一个没有使用的buffer
    gc_root_buffer   *last_unused; // 指向buf尾部

    gc_root_buffer    to_free;  // 待释放的垃圾
    gc_root_buffer   *next_to_free; 

    uint32_t gc_runs;   // gc运行次数
    uint32_t collected; // 已回收的垃圾数
} zend_gc_globals;

typedef struct _gc_root_buffer {
    zend_refcounted          *ref; //每个zend_value的gc信息
    struct _gc_root_buffer   *next;
    struct _gc_root_buffer   *prev;
    uint32_t                 refcount;
} gc_root_buffer;

发表评论

电子邮件地址不会被公开。 必填项已用*标注