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()清理碎片。

posted on 2021-01-21 20:04  鸥海  阅读(1027)  评论(0编辑  收藏  举报