php内存管理详解2013-09-06php的内存管理php和c最重要的区别就是是否控制内存指针.内存在php中, 设置一个字符串变量很简单: <?php $str = "hello world"; ?>, 字符串可以自由的修改, 拷贝, 移动. 在C中, 则是另外一种方式, 虽然你可以简单的用静态字符串初始化: char *str = "hello world"; 但是这个字符串不能被修改, 因为它存在于代码段. 要创建一个可维护的字符串, 你需要分配一块内存, 并使用一个strdup()这样的函数将内容拷贝到其中.
{ char *str; str = strdup("hello world"); if (!str) { fprintf(stderr, "Unable to allocate memory!"); }}传统的内存管理函数(malloc(), free(), strdup(), realloc(), calloc()等)不会被php的源代码直接使用, 本章将解释这么做的原因.释放分配的内存内存管理在以前的所有平台上都以请求/释放的方式处理. 应用告诉它的上层(通常是操作系统)"我想要一些内存使用", 如果空间允许, 操作系统提供给程序, 并对提供出去的内存进行一个记录.应用使用完内存后, 应该将内存还给OS以使其可以被分配给其他地方. 如果程序没有还回内存, OS就没有办法知道这段内存已经不再使用, 这样就无法分配给其他进程. 如果一块内存没有被释放, 并且拥有它的应用丢失了对它的句柄, 我们就称为"泄露", 因为已经没有人可以直接得到它了.在典型的客户端应用中, 小的不频繁的泄露通常是可以容忍的, 因为进程会在一段时间后终止, 这样泄露的内存就会被OS回收. 并不是说OS很牛知道泄露的内存, 而是它知道为已经终止的进程分配的内存都不会再使用.对于长时间运行的服务端守护进程, 包括apache这样的webserver, 进程被设计为运行很长周期, 通常是无限期的. 因此OS就无法干涉内存使用, 任何程度的泄露无论多小都可能累加到足够导致系统资源耗尽.考虑用户空间的stristr()函数; 为了不区分大小写查找字符串, 它实际上为haystack和needle各创建了一份小写的拷贝, 接着执行普通的区分大小写的搜索去查找相关的偏移量. 在字符串的偏移量被定位后, haystack和needle字符串的小写版本都不会再使用了. 如果没有释放这些拷贝, 那么每个使用stristr()的脚本每次被调用的时候都会泄露一些内存. 最终, webserver进程会占用整个系统的内存, 但是却都没有使用.完美的解决方案是编写良好的, 干净的, 一致的代码, 保证它们绝对正确. 不过在php解释器这样的环境中, 这只是解决方案的一半.错误处理为了提供从用户脚本的激活请求和所在的扩展函数中跳出的能力, 需要存在一种方法跳出整个激活请求. Zend引擎中的处理方式是在请求开始的地方设置一个跳出地址, 在所有的die()/exit()调用后, 或者碰到一些关键性错误(E_ERROR)时, 执行longjmp()转向到预先设置的跳出地址.虽然这种跳出处理简化了程序流程, 但它存在一个问题: 资源清理代码(比如free()调用)会被跳过, 会因此带来泄露. 考虑下面简化的引擎处理函数调用的代码:
void call_function(const char *fname, int fname_len TSRMLS_DC){zend_function *fe;char *lcase_fname;/* php函数是大小写不敏感的, 为了简化在函数表中对它们的定位, 所有的函数名都隐式的翻译为小写 */lcase_fname = estrndup(fname, fname_len);zend_str_tolower(lcase_fname, fname_len);if (zend_hash_find(EG(function_table),lcase_fname, fname_len + 1, (void **)&fe) == FAILURE) {zend_execute(fe->op_array TSRMLS_CC);} else {php_error_docref(NULL TSRMLS_CC, E_ERROR, "Call to undefined function: %s()", fname);}efree(lcase_fname);}当php_error_docref()一行执行到时, 内部的处理器看到错误级别是关键性的, 就调用longjmp()中断当前程序流, 离开call_function(), 这样就不能到达efree(lcase_fname)一行. 那你就可能会想, 把efree()行移动到php_error_docref()上面, 但是如果这个call_function()调用进入第一个条件分支呢(查找到了函数名, 正常执行)? 还有一点, fname自己是一个分配的字符串, 并且它在错误消息中被使用, 在使用完之前你不能释放它.php_error_docref()函数是一个内部等价于trigger_error(). 第一个参数是一个可选的文档引用, 如果在php.ini中启用它将被追加到docref.root后面. 第三个参数可以是任意的E_*族常量标记错误的严重程度. 第四个和后面的参数是符合printf()样式的格式串和可变参列表.Zend内存管理由于请求跳出(故障)产生的内存泄露的解决方案是Zend内存管理(ZendMM)层. 引擎的这一部分扮演了相当于操作系统通常扮演的角色, 分配内存给调用应用. 不同的是, 站在进程空间请求的认知角度, 它足够底层, 当请求die的时候, 它可以执行和OS在进程die时所做的相同的事情. 也就是说它会隐式的释放所有请求拥有的内存空间. 下图展示了在php进程中ZendMM和OS的关系: