今天上午剛到公司,就有同事在公司群裡反映某個計劃任務出現問題了。我就懷著刨根問底的心,去檢視了log。發現挺有意思的一個問題,PHP記憶體溢位導致指令碼執行失敗。那就一起來看個究竟吧!
- 首先檢視了計劃任務的Log
從報錯資訊字面意思可以看出,允許的134217728 bytes
的記憶體已經用盡,還要試圖分配12961640 bytes
記憶體。
給你(當前指令碼)分配的記憶體你已經用完了,你還想問系統要記憶體。系統這時想對你說:
地主家也沒有餘糧啊(借用葛優大爺的一句話)
- 模擬一下"案發現場"
- 新建一個mem_exhausted.php檔案 copy過來一個2.4M的log檔案做測試用
- 寫個簡單的指令碼重現"案發現場" 故意分配1M的記憶體 來讀取2.4M的log
- 執行指令碼,"案發現場"重現
- 新建一個mem_exhausted.php檔案 copy過來一個2.4M的log檔案做測試用
分析"事故"原因
指令碼一次性讀取了大量的資料(可能是讀的檔案,可能是讀取的資料庫)
如下圖: 往杯子(分配給當前指令碼的記憶體)裡面倒數水(log檔案的資料),杯子容量(記憶體)不夠用解決方案
a. 既然杯子小 就換個大杯子(增大給指令碼分配的記憶體)治標不治本:ini_set('memory_limit','100M');
b. 把水分批次倒入杯子中(迴圈,分段讀取資料,讀資料庫的話可以用limit)
看看結果
分段讀取也是可以解決問題滴
- 其他優化方案
- 應當儘可能減少靜態變數的使用,在需要資料重用時,可以考慮使用引用(&)。
- 資料庫操作完成後,要馬上關閉連線;
- 一個物件使用完,要及時呼叫解構函式(__destruct())
- 用過的變數及時銷燬(unset())掉
- 可以使用memory_get_usage()函式,獲取當前佔用記憶體 根據當前使用的記憶體來調整程式
- unset()函式只能在變數值佔用記憶體空間超過256位元組時才會釋放記憶體空間。(PHP核心的gc垃圾回收機制決定)
- 有當指向該變數的所有變數(如引用變數)都被銷燬後,才會釋放記憶體
(PHP變數底層實現是一個_zval_struct結構體,is_refgc表示引用計數 is_refgc表示是否為引用)