程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> 網頁編程 >> PHP編程 >> 關於PHP編程 >> php的擴展和嵌入--php內存管理

php的擴展和嵌入--php內存管理

編輯:關於PHP編程

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" class="keylink">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確實是一門相當神奇的語言。哈哈。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved