現象
在跑 edu_ocr_img 表的歸檔時,每跑幾萬個數據,都會報一次內存耗盡
PHP Fatal error: Allowed memory size of 134217728 bytesexhausted (tried toallocate 135168 bytes)
跟蹤代碼發現,是在插入時以下代碼造成的:
EduOCRTaskBackup::getDb()->createCommand()->batchInsert(EduOCRTaskBackup::tableName(), $fields, $data)->execute();
execute 之后會造成使用內存漲上去,并且在之后 unset 所有變量內存也會有一部分不會刪除,直到內存耗盡。
于是跟蹤到 Yii2中execute的具體代碼塊發現在記錄 log 的時候會將使用很高的內存,分析代碼之后得出造成泄漏的代碼塊如下:
造成泄漏的代碼塊
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
/** * Logs a message with the given type and category. * If [[traceLevel]] is greater than 0, additional call stack information about * the application code will be logged as well. * @param string|array $message the message to be logged. This can be a simple string or a more * complex data structure that will be handled by a [[Target|log target]]. * @param integer $level the level of the message. This must be one of the following: * `Logger::LEVEL_ERROR`, `Logger::LEVEL_WARNING`, `Logger::LEVEL_INFO`, `Logger::LEVEL_TRACE`, * `Logger::LEVEL_PROFILE_BEGIN`, `Logger::LEVEL_PROFILE_END`. * @param string $category the category of the message. */ public function log( $message , $level , $category = 'application' ) { $time = microtime(true); $traces = []; if ( $this ->traceLevel > 0) { $count = 0; $ts = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); array_pop ( $ts ); // remove the last trace since it would be the entry script, not very useful foreach ( $ts as $trace ) { if (isset( $trace [ 'file' ], $trace [ 'line' ]) && strpos ( $trace [ 'file' ], YII2_PATH) !== 0) { unset( $trace [ 'object' ], $trace [ 'args' ]); $traces [] = $trace ; if (++ $count >= $this ->traceLevel) { break ; } } } } // 這里是造成內存的罪魁禍首 $this ->messages[] = [ $message , $level , $category , $time , $traces ]; if ( $this ->flushInterval > 0 && count ( $this ->messages) >= $this ->flushInterval) { $this -> flush (); } } |
造成內存泄漏的原因分析
在 Yii2框架中的 vendor/yiisoft/yii2/log/Logger.php:156 log函數的156行之后會判斷 count($this->messages) >= $this->flushInterval
即:內存中存儲的 message 的條數要大于等于預設的 $this->flushInterval 才會將內存中的message 刷到磁盤上去。
如果在刷新到磁盤之前就已經將 php.ini 設置的 128M 內存打滿的話,會直接報錯申請內存耗盡。
很多關于 YII2其他原因的內存泄漏的討論
https://github.com/yiisoft/yii2/issues/13256
解決方案
在程序開始時,設置 flushInterval 為一個比較小的值
1
|
\Yii::getLogger()->flushInterval = 100; // 設置成一個較小的值 |
在程序執行過程中,每次 execute 之后對內存中的 message 進行 flush
1
|
\Yii::getLogger()-> flush (true); // 參數傳 true 表示每次都會將 message 清理到磁盤中 |
總結
以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,謝謝大家對服務器之家的支持。
原文鏈接:https://www.maiyewang.com/2019/11/30/yii2-框架跑腳本時內存泄漏問題分析/