php对内存的管理机制相当的详尽,它在这一点上更类似与java的垃圾回收机制。而对于c语言或者c++大部分时候都只能由程序员自己把申请的空间释放掉。在php中,由于要应对成千上万的连接,同时这些连接往往还需要保持很长的时间。这并不同于c中程序结束了相应的内存块就会被回收。
所以仅仅依靠程序员在写程序的时候注意内存回收是不够的,php肯定要有一些自己内部的、与连接相关的内存管理机制来保证不发生任何的内存泄露。
在本文中,首先对php的内存机制进行一个介绍:
那些在c语言中的空间函数,比如malloc() free() strdup() realloc() calloc(),php中会有不同的形式。
返还申请的内存:对于程序员来说,每一块申请的内存都应该返还,如果不还就会导致内存泄漏。在那些不要求一直运行的程序中,稍许的内存泄漏在整个进程被杀掉之后就结束了。但是类似于apache这种一直运行的web
server,小的内存泄漏最终会导致程序的崩溃。
错误处理的例子:
在进行错误处理的时候,采用的机制一般是是zend engine会设定一个跳出地址,一旦发生exit或die或任何严重错误e_error的时候,就会利用一个longjmp()跳到这个地址上面去。但是这种做法几乎都会导致内存泄漏。因为free的操作都会被跳掉。(这个问题在c++里面也同样存在,就是在设计类的时候,绝不要把错误处理或告警函数写在构造或者析构函数内,同样的原因,由于对象已经处在了销毁或创建的阶段,所以任何错误函数处理都可能打断这一过程,从而可能导致内存泄漏。)
下面的代码中就给出了这样的一个例子:
void call_function(const char *fname, int fname_len tsrmls_dc)
{
zend_function *fe;
char *lcase_fname;
/* php function names are case-insensitive to simplify locating them in the function tables all function names are implicitly
* translated to lowercase
*/
lcase_fname = estrndup(fname, fname_len);//创造一个函数名的副本
zend_str_tolower(lcase_fname, fname_len);//都转换成小写,这样的寻找的时候很方便,这应该也是php函数表中进行函数标识的方式。
if (zend_hash_find(eg(function_table),
lcase_fname, fname_len + 1, (void **)&fe) == failure) {?success。这个是要在函数表里面寻找待调用的函数。
zend_execute(fe->op_array tsrmls_cc);
} else {
php_error_docref(null tsrmls_cc, e_error,
“call to undefined function: %s()”, fname); //等同于trigger_error()
}
efree(lcase_fname);
}在这个例子中,提供了一个php在调用函数时候的功能。当php调用函数时,需要到函数表也就是function_table中去寻找相应的函数,而在寻找之前要先转换到小写字母,这样在寻找的时候可以提高查找的效率。
而通过zend_hash_find函数如果找到了要调用的函数,就使用zend_execute进行调用。而如果没找到的haunted就要跳出报错,显示没找到。但是问题来了,注意之前为了寻找函数创建了一个小写版本的函数名字符串。这个字符串一直到用到zend_hash_find函数,一旦没找到进入了报错之后,那么这个字符串所对应的内存空间必然就找不回来了,这就造成了内存的泄露。
因此,php提供了zend内存管理,zend memory management也称为zendmm。
php中的内存管理与操作系统的机制类似,但是对象是针对每一个请求所涉及的内存的。除此之外zendmm还会控制ini文件里面规定的memz喎?http://www.bkjia.com/kf/ware/vc/” target=”_blank” >vcnlfbgltaxsjrnkyvs3kx8u10ru1qco/upbh68fzy/nsqsfztcte2rtms6y5/chl1ek49m1lbw9yesbsaw1pdkosxmfdtnkyu+hj6sfryqew3kgjpgxppttazbzw0lxe1+7pwspmv7s1vchly/zt67lz1/fptc2zz+dbqs+1tctsu7ljoapv67bustnx98+1zbpw0lxeserxvlxexnq05snqx+u6zcrnt8w1xle9t6ijrhbocnbqtrzt0lbu06a1xlqvyv2ho9xi0km6r8r9sqkyu8rh0ru49rzytaw1xmzmu7ujrmv8w8fw0ld8uqzt0mzytqi1xndfz6kjrnta1elqqddfz6k1xldv1vrpwr7nxny5u7drw7+49sfrx/pl+cnqx+u1xmtatoa/6b340ncx6sq2oapv4th5vs3e3lm7yrxp1rbuw7+49sfrx/o1xmtatobh+npyvfjq0lfwsfc1xlncwo2hozxsat7nrmqx1nrnvnbqv7s1vchl0ru5ssg91tbe2rtmx+vh87xet73kvao6cgvyc2lzdgvudlrncgvylxjlcxvlc3sjrlbu09pwzxjzaxn0zw50wltltblusru24lj6z7xns7xex+vh877n0rvr+chlo6zssr7nysfltcrhtsdbottaw7/su7j2x+vh89auzek1xkossru74dtax+vh873hyvjwrrrzsbu72mrvoao1q8rh09dksbryyse38xblcnnpc3rlbns/yctc0qpydw50aw1lssxe3naqtccjrmv50ttu2txi1tbh6b/2z8kjrndo0qrsu7j2zmxhz8c01rjkvtxi0ru146gjtttt2srht/hkx3blcnnpc3rlbnsjrl340nde2rtmx+vh87xet73kvcrhsrvsu9h5tcsho8/cw+a4+lp2ttttprnyz7wjugokphvspgo8bgk+cgvtywxsb2moynvmzmvyx2xlbiwxksa9psbtywxsb2moynvmzmvyx2xlbik8bgk+cgvybwfsbg9jkgj1zmzlcl9szw4smckgpt0gzw1hbgxvyyhidwzmzxjfbgvukdxi1tbbqs+1ysftw7rqtqjs5bxet73kvb72tqi1xdxsat4jzgvmaw5lihblbwfsbg9jkhnpemuscgvyc2lzdgvudckgxdxsat4kcgo8bgk+cjx1bd4kkchwzxjzaxn0zw50kt9tywxsb2moc2l6zsk6zw1hbgxvyyhzaxplkskkcmzsywc9mbhtyr7kx3blcnnpc3rlbns1xkoszqowse3kvrk7ysejrl7nuprsu7djtcs4vcr009rh68fztcrlbwfsbg9j0rvr+chloamkpgjypgokcjxicj4kcs/czbzw0l/j0ts/tlw9z7xns7xexnq05snqx+u6r8r90+twahdw0lxexnq05snqx+u6r8r9tcs21lhi16q7u828o7okcjxpbwcgc3jjpq==”http://www.bkjia.com/uploadfile/collfiles/20131213/20131213091641239.jpg” alt=”\”>
如果你对malloc、calloc和realloc这些函数还不太熟悉,请移步:
http://www.cppblog.com/sandywin/archive/2011/09/14/155746.html
除此之外,还有两个安全模式的内存函数:
void *safe_emalloc(size_t size, size_t count, size_t addtl);
void *safe_pemalloc(size_t size, size_t count, size_t addtl, char persistent);
他们申请的空间是size*count + addtl,存在的原因是为了避免int型的溢出。
接下来说一个更有趣的,php中的引用计数:
很多语言中都有引用,很多时候也都会使用引用。通过引用可以节省空间,因为有时候并没有必要为每个变量都制造一个副本。
所谓引用计数,就是指同一块内存空间被多少个变量引用了,从而避免可能的内存错误操作。
先看下面的一段代码:
* {
* zval *helloval;
* make_std_zval(helloval);
* zval_string(helloval, “hello world”, 1);
* zend_hash_add(eg(active_symbol_table), “a”, sizeof(“a”),
* &helloval, sizeof(zval*), null);
* zend_hash_add(eg(active_symbol_table), “b”, sizeof(“b”),
* &helloval, sizeof(zval*), null);
* }
这段代码首先声明了一个zval变量,再用make_std_zval进行了初始化,接下来用zval_string附了初值。然后对这个变量,给出了两个变量名。第一个是a,第二个是b,毫无疑问,第二个肯定是一个引用。但是这段代码这么写肯定有问题,问题就在于你在用zend_hash_add之后并没有更新相应的引用计数。zend并不知道你多加了这么一个引用,这就导致释放内存的时候可能导致两次释放。所以经过修改之后的正确代码如下:
* {
* zval *helloval;
* make_std_zval(helloval);
* zval_string(helloval, “hello world”, 1);
* zend_hash_add(eg(active_symbol_table), “a”, sizeof(“a”),
* &helloval, sizeof(zval*), null);
* zval_addref(helloval);//加上这个之后,就不会有重新释放同一块内存空间这样的错误了
* zend_hash_add(eg(active_symbol_table), “b”, sizeof(“b”),
* &helloval, sizeof(zval*), null);
* }
进行了zval_addref之后,下一次unset变量的时候,会先查看ref_count引用计数,如果=1就释放,如果>1就只是-1,并不进行内存释放。
copy on write
再来看下面的这一段php代码:
很显然在第二行的时候b声明了一个a的引用,那么在执行完了第三行的代码之后,b增加了,a增不增加呢?很多时候可能并不想增加。所以这个时候当zend检测到refcount>1之后,就会执行一个变量分离的操作,把原来的一块内存变成两块内存:
zval *get_var_and_separate(char *varname, int varname_len tsrmls_dc)
{
zval **varval, *varcopy;
if (zend_hash_find(eg(active_symbol_table),
varname, varname_len + 1, (void**)&varval) == failure) {
/*符号表里没找到 */
return null;
}
if ((*varval)->refcount < 2) {
/* varname 是唯一的引用,什么也不用做 */
return *varval;
}
/* 否则的话,不是唯一的引用,给zval*做一个副本 */
make_std_zval(varcopy);
varcopy = *varval;
/* duplicate any allocated structures within the zval* */
zval_copy_ctor(varcopy); //这一块是怎么拷贝的?mark 应该已经跟varval对应的varname连起来了
/* 把varname的版本删掉,这会减少varval的引用次数 */
zend_hash_del(eg(active_symbol_table), varname, varname_len + 1);
/* 初始化新创造的值的引用次数,然后附给varname变量 */
varcopy->refcount = 1;
varcopy->is_ref = 0;
zend_hash_add(eg(active_symbol_table), varname, varname_len + 1,
&varcopy, sizeof(zval*), null);
/* return the new zval* */
return varcopy;
}首先看到了两个判断语句,第一个判断语句先在符号表里面看看有没有找到相应的变量,如果没找到也就没必要分离了。第二个判断语句是看输入的变量的引用次数是不是小于2,如果是的话那就说明输入变量*varval是唯一的,也没必要分离。
否则的话肯定有引用,这个时候就要制作一个副本varcopy。这个副本会承袭varname对应的值,但是不同之处在于帮它重新申请了内存空间,重新初始化了refcount和is_ref参数。
以a、b为例,在$b+=5,执行之后,b作为varname去寻找是否有引用,发现还有一个引用a,这个时候就把b的值拷出来,然后重新申请一片空间,在重新注册为b。这样的话就是两块独立的内存块了。
change on write
再看一个代码片段:
如果你觉得想要a跟着b一起改变,那没有问题,只要显式的用&符号进行引用声明就可以了。这样的话is_ref标志位就会被置1. 这时候也就没必要进行内存块的分离了。所以在上面的代码中要把第二个if语句的判断更改一下:
if ((*varval)->is_ref || (*varval)->refcount < 2) {
/* varname is the only actual reference,
* or it's a full reference to other variables
* either way: no separating to be done
*/
return *varval;
}
再看最后一种情况,这种情况最纠结:
既不是copy on write也不是change on wirte,那没办法了,只好分离一下。这里只好b独立出来了:
对php内存管理的一些机制就说到这里,感觉php确实是一门相当神奇的语言。哈哈。
http://www.bkjia.com/phpjc/621625.htmlwww.bkjia.comtruehttp://www.bkjia.com/phpjc/621625.htmltecharticlephp对内存的管理机制相当的详尽,它在这一点上更类与java的垃圾回收机制。而对于c语言或者c大部分时候都只能由程序员自己把申请的空间…