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

wujunze發表於2017-08-05

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

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

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

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

geyou.png
geyou.png

  1. 模擬一下"案發現場"
    • 新建一個mem_exhausted.php檔案 copy過來一個2.4M的log檔案做測試用
      log_size.png
      log_size.png
    • 寫個簡單的指令碼重現"案發現場" 故意分配1M的記憶體 來讀取2.4M的log
      test_mem_1.png
      test_mem_1.png
    • 執行指令碼,"案發現場"重現
      test_run_res.png
      test_run_res.png
  1. 分析"事故"原因
    指令碼一次性讀取了大量的資料(可能是讀的檔案,可能是讀取的資料庫)
    如下圖: 往杯子(分配給當前指令碼的記憶體)裡面倒數水(log檔案的資料),杯子容量(記憶體)不夠用

    water_overflow.jpg
    water_overflow.jpg

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

    new_1.png
    new_1.png

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

code_1.png
code_1.png

看看結果

run_res_new.png
run_res_new.png

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

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

相關文章