PHP7 扩展开发(五) 内存管理概述

编辑于 2016-10-02

* 移动设备下, 可左滑手指以查看较宽代码

这一块自己也没深入,  只介绍基础的内容.

在 PHP 扩展中使用内存

用于从堆中分配内存的 PHP API 几乎和标准C API一样. 在编写扩展的时候, 使用下面与C语言对应的API函数:

emalloc(size_t size);
efree(void *ptr);
ecalloc(size_t nmemb, size_t size);
erealloc(void *ptr, size_t size);
estrdup(const char *s);
estrndup(const char *s, unsigned int length);

这些函数的优点是: 任何分配的内存在偶然情况下如果没有被释放, 则会在页面请求的最后被释放. 因此, 真正的内存泄漏不会产生. 然而, 不要依赖这一机制, 从调试和性能两个原因来考虑, 应当确保释放应该释放的内存. 你不需要检查这些内存分配函数的返回值是否为 null. 当内存分配失败, 它们会发出 E_ERROR 错误, 从而决不会返回到扩展. 剩下的优点是在多线程环境下性能的提高, 调试模式下检测内存错误等.

有一种情况, 那就是扩展需要分配在请求中永久存在的内存, 这时不得不使用C语言的 malloc().

内存管理器

PHP 的内核中的内存管理逻辑被称作Zend Memory Manager, 直译为内存管理器. 分为存储层, 堆层 ,接口层:

接口层 (emalloc/efree), 实现了上面的几个内存接口函数.

堆层 (heap), 维护了三个列表:

小块内存列表 free_buckets

大块内存列表 large_free_buckets

剩余内存列表 rest_buckets

它把内存非为小块内存和大块内存两种, 对于小块内存, 这部分是最常用的, 所以追求高性能. 而对于大块内存, 则追求的是稳妥, 尽量避免内存浪费.

内存管理器会在三种列表中小到大搜索合适大小的内存块.

存储层 (storage), 内存分配的方式对堆层透明化, 实现存储层和 heap 层的分离.



php 大内存列表

比如大块内存列表 large_free_buckets 是一个建树和双向列表的结合, 它使用一个宏来决定某个大小的内存, 落在什么 index 上. 比如对于 large_free_buckets[2], 就只会保存, 大小在0b1000到0b1111大小的内存. 再比如: large_free_buckets[6], 就保存着大小为0b10000000到0b11111111大小的内存的指针. 这样, 再分配内存的时候, 内存管理器就可以快速定位到最可能适合的区域来查找. 提高性能.

而二叉查找树, 则是为了迅速的找到 block 块. 如果没有找到, 会分配一个最小的大块内存.

还有一个 rest_buckets, 这个结构是个双向列表, 用来保存一些 PHP 分配后剩下的内存, 避免无意义的把剩余内存插入 free_buckets 带来的性能问题.

内存垃圾回收

当程序 unset 一个变量或者是其他的释放行为时, 内存管理器并不会直接立刻将内存交回给系统, 而是只在自身维护的内存池中将其重新标识为可用(当然要结合引用计数refcount), 按照内存的大小整理到上面所说的三种列表之中, 以备下次内存申请时使用.

PHP5.3 中的垃圾回收算法是: Concurrent Cycle Collection in Reference Counted Systems(引用计数系统中的同步周期回收).

此算法详情: http://docs.php.net/manual/zh/features.gc.collecting-cycles.php

不理解也没关系, 它达到了以下目的:

并不需要每次 refcount 减少时都进入回收周期

可以解决循环引用问题

可以总将内存泄露保持在一个阈值以下