php記憶體管理詳解

來源:互聯網
上載者:User

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

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.