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

php內存管理詳解

編輯:PHP綜合

php的內存管理

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的關系:

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