PHP的内存管理与垃圾回收机制
一、内存管理机制
PHP内存管理器
概述
1) 操作系统直接管理着内存,所以操作系统也需要进行内存管理,
计算机中通常都有内存管理单元(MMU) 用于处理CPU对内存的访问。
2) 应用程序无法直接调用物理内存, 只能向系统申请内存。
向操作系统申请内存空间会引发系统调用。系统调用会将CPU从用户态切换到内核。
为了减少系统调用开销。通常在用户态进行内存管理。
申请大块内存备用。使用完的内存不马上释放,将内存复用,避免多次内存申请和释放所带来性能消耗。
3) PHP不需要显示内存管理,由Zend引擎进行管理。
PHP内存限制
1)php.ini中的默认32MB
memory_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_startup
PHP内存管理维护三个列表:
1)小块内存列表 free_buckets
2)大块内存列表 large_free_buckets
3)剩余内存列表 rest_buckets
ZendMM向系统进行的内存申请,并不是有需要时向系统即时申请, 而是由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容器中的refcount在减少,并没有减到0,PHP会把该值放到缓冲区,当做有可能是垃圾的怀疑对象
当根缓冲区达到临界值:
PHP会对每个根缓冲区中的根zval按照深度优先遍历算法遍历所有能遍历到的zval,如果发现refcount=0就当做垃圾清理掉。
改进解决了:1.可以将内存泄露保持在一个阈值以下;2.循环引用问题。
与垃圾回收算法相关的PHP配置
1、可以通过修改php.ini中的zend.enable_gc来打开或关闭PHP的垃圾回收机制,
也可以通过调用gc_enable()或gc_disable()打开或关闭PHP的垃圾回收机制。
2、在PHP5.3中即使关闭了垃圾回收机制,PHP仍然会记录可疑根zval到根缓冲区,
只是当根缓冲区满额时,PHP不会自动运行垃圾回收。
3、当然,任何时候您都可以通过手工调用gc_collect_cycles()函数强制执行内存回收。