首页
下载
文档
社区
视频
捐赠
源代码
赞助商
AOT 编译器
AI 助理
商业产品
PHP AOT 原生编译器
Swoole-Compiler 代码加密器
CRMEB 新零售社交电商系统
登录
注册
全部
提问
分享
讨论
建议
公告
开发框架
CodeGalaxy
发表新帖
PHP常驻进程,内存泄漏排查指南
## 废话不多说,总共分三步 ### 1、初步定为泄漏: - 迫于 996ICU 的压力,广大的 PHPer 一般不会关注泄漏问题,都是在看到报错 ```bash Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 12288 bytes) ``` 才发现泄漏问题,此时我们一般会通过查看进程的`RSS`占用来确定内存占用,例如这样 ```bash cat /proc/28806/status |grep RSS ``` 但**一定要注意**的是,此处查看的`RSS`是包含共享内存的(共享的内存会重复计算多次),并不是进程真正占用的内存,`USS`才是我们 PHP 代码申请的内存,我们更应该关注的是`USS`指标。(感兴趣的小伙伴可以看[这个视频](https://course.swoole-cloud.com/course-video/85))。 - 怎么看`USS`,这里推荐`smem`这个命令,用法和结果如下:  RSS 占用特别多,USS 特别少,证明大部分 RSS 都是共享内存占用,此时大概率是你的`Swoole Table`或者`Apcu`用的有问题,因为这两个底层是基于共享内存的。 ### 2、定位泄漏的代码: 可以用`Swoole Tracker`提供的工具来定位,具体参考[这篇文章](https://wenda.swoole.com/detail/107688) ### 3、清理内存碎片: 如果 Tracker 发现不了泄漏,内存还一直涨,八成是遇到了 PHP 的内存碎片问题,内存碎片问题也是我想写这篇文章的原因,社区里面有个小伙伴用了[Swoole Tracker](https://business.swoole.com/tracker/index)没有发现泄漏,但是通过 smem 命令查看内存确实在涨,即使`unset`了所有变量,内存仍然无法降下去,代码如下: ```php 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下面的内存碎片导致的内存增长问题,但是由于zend mm的设计机制问题,长时间运行还是会缓慢增长,这里我们也可以通过替换 PHP 的内存管理模块(比如采用 jemalloc)来彻底避免这种问题,具体方法是自己编译一个jemalloc的so然后启动PHP的时候这样`export USE_ZEND_ALLOC=0 && LD_PRELOAD='/usr/local/lib/libjemalloc.so' php /home/guoxinhua/swoole_server.php`即可。 ## 总结 第一步正确的发现泄漏,第二步使用 tracker 定位泄漏代码并 fix 它,第三步如果还是泄漏,尝试调用`gc_mem_caches()`清理碎片。 希望此文章能帮到你^_^
发布于5年前 · 317 次浏览 · 来自
分享
郭新华
## 废话不多说,总共分三步 ### 1、初步定为泄漏: - 迫于 996ICU 的压力,广大的 PHPer 一般不会关注泄漏问题,都是在看到报错 ```bash Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 12288 bytes) ``` 才发现泄漏问题,此时我们一般会通过查看进程的`RSS`占用来确定内存占用,例如这样 ```bash cat /proc/28806/status |grep RSS ``` 但**一定要注意**的是,此处查看的`RSS`是包含共享内存的(共享的内存会重复计算多次),并不是进程真正占用的内存,`USS`才是我们 PHP 代码申请的内存,我们更应该关注的是`USS`指标。(感兴趣的小伙伴可以看[这个视频](https://course.swoole-cloud.com/course-video/85))。 - 怎么看`USS`,这里推荐`smem`这个命令,用法和结果如下:  RSS 占用特别多,USS 特别少,证明大部分 RSS 都是共享内存占用,此时大概率是你的`Swoole Table`或者`Apcu`用的有问题,因为这两个底层是基于共享内存的。 ### 2、定位泄漏的代码: 可以用`Swoole Tracker`提供的工具来定位,具体参考[这篇文章](https://wenda.swoole.com/detail/107688) ### 3、清理内存碎片: 如果 Tracker 发现不了泄漏,内存还一直涨,八成是遇到了 PHP 的内存碎片问题,内存碎片问题也是我想写这篇文章的原因,社区里面有个小伙伴用了[Swoole Tracker](https://business.swoole.com/tracker/index)没有发现泄漏,但是通过 smem 命令查看内存确实在涨,即使`unset`了所有变量,内存仍然无法降下去,代码如下: ```php 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下面的内存碎片导致的内存增长问题,但是由于zend mm的设计机制问题,长时间运行还是会缓慢增长,这里我们也可以通过替换 PHP 的内存管理模块(比如采用 jemalloc)来彻底避免这种问题,具体方法是自己编译一个jemalloc的so然后启动PHP的时候这样`export USE_ZEND_ALLOC=0 && LD_PRELOAD='/usr/local/lib/libjemalloc.so' php /home/guoxinhua/swoole_server.php`即可。 ## 总结 第一步正确的发现泄漏,第二步使用 tracker 定位泄漏代码并 fix 它,第三步如果还是泄漏,尝试调用`gc_mem_caches()`清理碎片。 希望此文章能帮到你^_^
赞
1
收藏
提问
分享
讨论
建议
公告
开发框架
CodeGalaxy
登录
后参与评论
评论
2021-03-02
卡大大
长知识了
赞
2
回复
2021-09-04
郭新华
补充一下 jemalloc会有释放内存给os不积极的情况,设置环境变量 `background_thread:true,dirty_decay_ms:0,muzzy_decay_ms:0` 可以有效调动gc积极性
赞
2
回复
2022-10-31
w
wenlongh
Swoole Tracker 工具不是免费?
赞
1
回复
2022-11-09
自在侠
长知识了,非常好
赞
0
回复