誰動了我的記憶體之 PHP 記憶體溢位

CryptoPanda發表於2017-04-05

今天上午剛到公司,就有同事在公司群裡反映某個計劃任務出現問題了。我就懷著刨根問底的心,去檢視了log。發現挺有意思的一個問題,PHP記憶體溢位導致指令碼執行失敗。那就一起來看個究竟吧!

  1. 首先檢視了計劃任務的Log

誰動了我的記憶體之 PHP 記憶體溢位

從報錯資訊字面意思可以看出,允許的134217728 bytes的記憶體已經用盡,還要試圖分配12961640 bytes記憶體。
給你(當前指令碼)分配的記憶體你已經用完了,你還想問系統要記憶體。系統這時想對你說:

地主家也沒有餘糧啊(借用葛優大爺的一句話)

誰動了我的記憶體之 PHP 記憶體溢位

  1. 模擬一下"案發現場"
    • 新建一個mem_exhausted.php檔案 copy過來一個2.4M的log檔案做測試用

誰動了我的記憶體之 PHP 記憶體溢位

  • 寫個簡單的指令碼重現"案發現場" 故意分配1M的記憶體 來讀取2.4M的log

誰動了我的記憶體之 PHP 記憶體溢位

  • 執行指令碼,"案發現場"重現

誰動了我的記憶體之 PHP 記憶體溢位

  1. 分析"事故"原因
    指令碼一次性讀取了大量的資料(可能是讀的檔案,可能是讀取的資料庫)
    如下圖: 往杯子(分配給當前指令碼的記憶體)裡面倒數水(log檔案的資料),杯子容量(記憶體)不夠用

誰動了我的記憶體之 PHP 記憶體溢位

  1. 解決方案
    a. 既然杯子小 就換個大杯子(增大給指令碼分配的記憶體)治標不治本: ini_set('memory_limit','100M');

誰動了我的記憶體之 PHP 記憶體溢位

b. 把水分批次倒入杯子中(迴圈,分段讀取資料,讀資料庫的話可以用limit)

誰動了我的記憶體之 PHP 記憶體溢位

看看結果

誰動了我的記憶體之 PHP 記憶體溢位

分段讀取也是可以解決問題滴

  1. 其他最佳化方案
    • 應當儘可能減少靜態變數的使用,在需要資料重用時,可以考慮使用引用(&)。
    • 資料庫操作完成後,要馬上關閉連線;
    • 一個物件使用完,要及時呼叫解構函式(__destruct())
    • 用過的變數及時銷燬(unset())掉
    • 可以使用memory_get_usage()函式,獲取當前佔用記憶體 根據當前使用的記憶體來調整程式
    • unset()函式只能在變數值佔用記憶體空間超過256位元組時才會釋放記憶體空間。(PHP核心的gc垃圾回收機制決定)
    • 有當指向該變數的所有變數(如引用變數)都被銷燬後,才會釋放記憶體
      (PHP變數底層實現是一個_zval_struct結構體,refcount_gc表示引用計數 is_ref__gc表示是否為引用)
本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章