个人技术日记 - 2017年10月
type: channel
link: https://www.faxwo.com/2017/10/
description: 关注互联网新技术与发展,分享福利和心得.
items: PHP的内存管理与垃圾回收机制

PHP的内存管理与垃圾回收机制
type: item
date: 2017-10-03T13:38:00+08:00
description: 一、内存管理机制PHP内存管理器概述1） 操作系统直接管理着内存，所以操作系统也需要进行内存管理，计算机中通常都有内存管理单元(MMU) 用于处理CPU对内存的访问。2） 应用程序无法直接调用物理内存， 只能向系统申请内存。向操作系统申请内存空间会引发系统调用。系统调用会将CPU从用户态切换到内核。为了减少系统调用开销。通常在用户态进行内存管理。 申请大块内存备用。使用完的内存不马上释放，将内存复用，避免多次内存申请和释放所带来性能消耗。3） PHP不需要显示内存管理，由Zend引擎进行管理。PHP内存限制1）php.ini中的默认32MBmemory_limit = 32M;2）动态修改内存ini_set("memory_limit", "128M");3）获取目前内存占用memory_get_usage([ bool $real_usage = false ]) : 如果设置为 TRUE，获取系统分配的真实内存尺寸。如果未设置或者设置为 FALSE，将是 emalloc() 报告使用的内存量。memory_get_peak_usage() ：返回当前脚本到目前位置所占用的内存峰值。接口层是一些宏定义，emalloc申请内存,efree释放回收内存。堆层 heap 全局符号表、局部符号表、常量表、函数表都在堆层heap内。_zend_mm_heap初始化内存，调用 zend_mm_startupPHP内存管理维护三个列表:1)小块内存列表 free_buckets2)大块内存列表 large_free_buckets3)剩余内存列表 rest_bucketsZendMM向系统进行的内存申请，并不是有需要时向系统即时申请， 而是由ZendMM的最底层（heap层）先向系统申请一大块的内存，通过对上面三种列表的填充， 建立一个类似于内存池的管理机制。 在程序运行需要使用内存的时候，ZendMM会在内存池中分配相应的内存供使用。 这样做的好处是避免了PHP向系统频繁的内存申请操作。ZendMM对内存分配的处理步骤：1）内存检查；2）命中缓存，找到内存块，调至步骤5；3）在ZendMM管理的heap层存储中搜索合适大小的内存块, 是在三种列表中小到大进行的，找到block后，调至步骤5;4）步骤3未找到内存，则使用 ZEND_MM_STORAGE_ALLOC 申请新内存块 （至少为ZEND_MM_SEG_SIZE）,进行步骤6;5）使用zend_mm_remove_from_free_list函数将已经使用block节点在zend_mm_free_block中移除;6） 内存分配完毕，对zend_mm_heap结构中的各种标识型变量进行维护，包括large_free_buckets， peak，size等;7） 返回分配的内存地址;存储层 storage内存分配的方式对堆层透明化，实现storage存储层和heap层的分离。 不同的内存分配方案，有对应的处理函数。内存的销毁ZendMM在内存销毁的处理上采用与内存申请相同的策略，当程序unset一个变量或者是其他的释放行为时， ZendMM并不会直接立刻将内存交回给系统，而是只在自身维护的内存池中将其重新标识为可用， 按照内存的大小整理到上面所说的三种列表（small,large,free）之中，以备下次内存申请时使用。ZendMM将内存块以整理收回到zend_mm_heap的方式，回收到内存池中。程序使用的所有内存，将在进程结束时统一交还给系统。PHP5.3之前PHP会根据refcount值来判断是不是垃圾,如果refcount值为0，PHP会当做垃圾释放掉这种回收机制有缺陷，对于环状引用的变量无法回收:$attr = array("hello"); $attr[]= &$attr; xdebug_debug_zval('attr'); echo PHP_EOL; 输出：attr:(refcount=2, is_ref=1),array (size=2) 0 => (refcount=1, is_ref=0),string'hello' (length=5) 1 => (refcount=2, is_ref=1),&array PHP5.3及之后首先PHP会分配一个固定大小的“根缓冲区”，这个缓冲区用于存放固定数量的zval，这个数量默认是10,000，如果需要修改则需要修改源代码Zend/zend_gc.c中的常量GC_ROOT_BUFFER_MAX_ENTRIES然后重新编译。改进了垃圾回收机制：如果发现一个zval容器中的refcount在增加，说明不是垃圾如果发现一个zval容器中的refcount在减少，如果减到了0，直接当做垃圾回收如果发现一个zval容器中的refco…