1、初步定為泄漏:
- 迫于 996ICU 的壓力,廣大的 PHPer 一般不會關注泄漏問題,都是在看到報錯
Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 12288 bytes)
才發現泄漏問題,此時我們一般會通過查看進程的RSS占用來確定內存占用,例如這樣
cat /proc/28806/status |grep RSS
但一定要注意的是,此處查看的RSS是包含共享內存的(共享的內存會重復計算多次),并不是進程真正占用的內存,USS才是我們 PHP 代碼申請的內存,我們更應該關注的是USS指標。(感興趣的小伙伴可以看這個視頻)。
- 怎么看
USS,這里推薦smem這個命令,用法和結果如下:

RSS 占用特別多,USS 特別少,證明大部分 RSS 都是共享內存占用,此時大概率是你的Swoole Table或者Apcu用的有問題,因為這兩個底層是基于共享內存的。
2、定位泄漏的代碼:
可以用Swoole Tracker提供的工具來定位,具體參考這篇文章
3、清理內存碎片:
如果 Tracker 發現不了泄漏,內存還一直漲,八成是遇到了 PHP 的內存碎片問題,內存碎片問題也是我想寫這篇文章的原因,社區里面有個小伙伴用了Swoole Tracker沒有發現泄漏,但是通過 smem 命令查看內存確實在漲,即使unset了所有變量,內存仍然無法降下去,代碼如下:
function main() { for ($i = 1; $i < 2000000; $i++) { $GLOBALS[$i] = str_repeat("str_repeat這個函數會申請內存,但我馬上就unset掉", 10); } for ($i = 1; $i < 2000000; $i++) { unset($GLOBALS[$i]); } } main();
咋回事呢?根本原因是產生了內存碎片,和 PHP 的內存分配算法有關,這里不展開講,大概原理是:小于 3072 字節的內存申請 PHP 會認為是小內存,PHP 會把所有申請的小內存塊緩存起來,即使釋放了也不歸還給操作系統,以保證內存管理的效率
在 FPM 下請求結束后會釋放所有內存,大部分歸還給系統,只保留一小部分,但是在 Cli 下沒有這樣的機制,我們該怎么辦?
我研究了一天,貌似只能給 php-src 提 pr 了,寫了一天發現"咦?"怎么有段代碼怎么和我的思路這么相似,仔細一看"日哦",在 php 高版本提供了一個gc_mem_caches()函數(網上沒有任何文章介紹這個函數),可以手動清理這種小內存的碎片問題 - -!
我們可以設置一個 Swoole 的定時器,定期調用gc_mem_caches()即可。
除了gc_mem_caches()我們也可以通過擴展替換 PHP 的內存管理模塊(比如采用 jemalloc)來避免這種問題,注意所有的內存管理算法都有內存碎片的問題,需要當心。
總結
第一步正確的發現泄漏,第二步使用 tracker 定位泄漏代碼并 fix 它,第三步如果還是泄漏,嘗試調用gc_mem_caches()清理碎片。
浙公網安備 33010602011771號