PHP深入理解-内存管理

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/lzx_victory/article/details/86410879

内存管理的主要目的:提高内存利用率,减少内存碎片,提高内存分配的速度.

内存设计

最大的可分配单元是chunk,大小为2MB.每个chunk分配了512个page,page的大小为4KB.
zend的page_size与Linux一般的page_size大小相等.

zend内部使用了mmap分配内存,不足一内存页的mmap强制以Linux的page_size进行对齐.
mmap可以指定这块内存在不同进程之间的处理方式,是共享还是写时拷贝.

相较于malloc分配方式,提供了文件到内存映射的功能.malloc分配不是基于page_size对齐的.

#define ZEND_MM_CHUNK_SIZE (2 * 1024 * 1024)               /* 2 MB  */ 
#define ZEND_MM_PAGE_SIZE  (4 * 1024)                      /* 4 KB  */
#define ZEND_MM_PAGES      (ZEND_MM_CHUNK_SIZE / ZEND_MM_PAGE_SIZE)  /* 512 */

内存分配三种模式

  • small:小于等于3KB的内存
  • large:大于3KB小于等于(2MB减去4KB)内存
  • huge:大于2MB减去4KB内存

每个chunk中的4KB用于zend_mm_chunk结构体分配.

内存对齐

内存对其的意义:根据给定的内存地址快速定位在申请内存的位置.
实际上再申请的时候为了保证内存对齐,会多申请一块内存,然后在这块内存找到对齐的地址,除这个地址+申请的地址大小外的内存都将要被释放.

zend提供了以下三种计算对齐的方式.

#define ZEND_MM_ALIGNED_OFFSET(size, alignment) \
	(((size_t)(size)) & ((alignment) - 1)) //得出基于对齐的起始地址的offset
#define ZEND_MM_ALIGNED_BASE(size, alignment) \
	(((size_t)(size)) & ~((alignment) - 1)) //得出对齐的起始地址
#define ZEND_MM_SIZE_TO_NUM(size, alignment) \
	(((size_t)(size) + ((alignment) - 1)) / (alignment)) //根据size,alignment得出需分配的个数

内存数据结构

zend_mm_heap:
全局变量alloc_globals.mm_heap指向zend_mm_heap数据结构

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
	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

	zend_mm_huge_list *huge_list;               /* list of huge allocated blocks */

	zend_mm_chunk     *main_chunk;
	zend_mm_chunk     *cached_chunks;			/* list of unused chunks */
	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 */
#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 {
	zend_mm_heap      *heap;//AG()里的mm_heap地址
	zend_mm_chunk     *next;//下一个trunk
	zend_mm_chunk     *prev;//之前的trunk
	int                free_pages;				/* number of free pages */
	int                free_tail;               /* number of free pages at the end of chunk 
    最后一块连续可用的page*/
	int                num; //当前chunk的序号
	char               reserve[64 - (sizeof(void*) * 3 + sizeof(int) * 3)];
	zend_mm_heap       heap_slot;               /* 只用于mainchunk used only in main chunk */
	zend_mm_page_map   free_map;                /* 空闲页的位图512 bits or 64 bytes */
	zend_mm_page_info  map[ZEND_MM_PAGES];      /* 存储每个页的使用信息,高两位代表使用内存的类型,低十位区分是否连续的页 2 KB = 512 * 4 */
};

内存分配释放逻辑

如之前所述,内存分配分为三种不同的范围.

huge

分配
1.申请size需要根据page_size进行对齐
2.对齐后的size再根据chunk_size大小进行对齐
3.将内存挂载到alloc_global.mm_heap->huge_list上

释放: 从huge_list链表中删除,调用munmap释放.

large

large分配是page分配的整数倍.

1.遍历双向链表alloc_global.mm_heap->main_trunk
2.如果free_pages小于要申请的页的个数回到1.
3.根据zend_mm_chunk->free_map查找最优连续page(连续page个数最少,连续page编号最少).
4.如果查找可分配的页则返回对应的地址,并将map[page_num]标记为large内存
5.如果chunk都没有可分配内存,就新申请一个chunk,在进行分配.

释放:
将zend_mm_chunk->free_map[page_num],zend_mm_chunk->map[page_num]置为0.
然后修改free_pages.如果pages都释放,那么释放chunk.

small分配

small类型共分为30种不同的大小.

mm_heap->free_slot数组结构存着每个slot类型链表的首地址.

1.根据申请的内存查找对应的规格表
2.根据规格表中的num,如果mm_heap->free_slot[num]为空则继续下一步,如果不为空返回对应的地址,并从mm_heap->free_slot[num]指向链表的首地址删除
3.申请的规格表中对应的页数(bin_pages[bin_num])并更新mm_chunk->map[page_num]标识位为small内存.第一个页需要设置map[page_num](位于map的24bit-16bit位段)设置free_slot个数.接下的连续页的标志位给予顺序标志(位于map的24bit-16bit位段).

释放时:
直接插入mm_heap->free_slot当中.

GC机制

slot

依次遍历mm_heap->free_slot.
根据slot的地址执行上述的查找的宏,找到对应的chunk地址以及当前slot所在的page_num,读取map[page_num].
如果是slot连续申请的规格表中对应的页数(bin_pages[bin_num]),那么位于第一页的map的24bit-16bit位段,会用于存储这几个连续页的free_slot个数.其余的页的map的24bit-16bit位段用存储连续页的页号.

如果map中的free_slot个数+1等于规格表中(bin_elements[num])存在的元素个数,那么删除heap->free_slot[num]链表中该slot.

chunk的遍历每个page,如果是small内存的第一页(map[page_num]&0x80000000),取24-16位判断free_slot个数等于这个规格表规定的slot个数.释放这几个连续的页.

chunk

循环遍历chunk链表如果chunk->free_pages = 511那么就释放这个chunk.

猜你喜欢

转载自blog.csdn.net/lzx_victory/article/details/86410879
今日推荐