V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
JJstyle
V2EX  ›  PHP

PHP 中如何定位内存泄露的代码所在行呢?

  •  
  •   JJstyle · 2020-08-13 18:20:47 +08:00 · 3979 次点击
    这是一个创建于 1563 天前的主题,其中的信息可能已经有所发展或是发生改变。

    phper 们应该都会自动内存泄露主要由于循环应用导致的,比如如下代码将会导致内存泄露:

    function fun1()
    {
      $a = ['hello'];
      $a[] = &$a;
    }
    
    func1();
    

    自己的代码还好,可以检查检查,由于使用了第三方依赖,怎么在依赖的文件中定位到有这种有内存泄露的代码呢?小弟实在想不出有什么好的办法

    第 1 条附言  ·  2020-08-14 01:28:06 +08:00

    上面的示例代码不是特别好,请看下面:

    <?php
    
    function fun1()
    {
      $a = ['hello'];
      $a[] = &$a;
    }
    
    while(true) {
        fun1();
        echo memory_get_usage() . "\n";
        sleep(1);
    }
    

    有兴趣的朋友可以试试上面的代码,内存将会不断增加(centos7 php7.4)

    ➔ /usr/local/opt/[email protected]/bin/php a.php
    1230440
    1230880
    1231288
    1231696
    1232104
    1232512
    1232920
    1233328
    1233736
    
    18 条回复    2020-08-14 18:19:31 +08:00
    leven87
        1
    leven87  
       2020-08-13 18:41:01 +08:00
    memory_get_usage ? 不懂
    知名一些的第三方应用,都有 github 或者论坛吧。
    sagaxu
        2
    sagaxu  
       2020-08-13 18:43:58 +08:00 via Android
    循环引用不会导致内存泄露
    JJstyle
        3
    JJstyle  
    OP
       2020-08-13 19:06:53 +08:00
    @sagaxu 你可以执行我上面的那段代码试试内存使用
    ben1024
        4
    ben1024  
       2020-08-13 19:19:09 +08:00
    xdebug 监听函数 memory_get_usage(true)/1024/1024/8 的内存值?
    wangritian
        5
    wangritian  
       2020-08-13 19:28:11 +08:00   ❤️ 1
    apache/php-fpm/swoole 应该都有一个叫 max_request 的参数,执行一定量请求后重启进程,哪怕代码有内存泄漏也会还给系统
    cholerae
        6
    cholerae  
       2020-08-13 19:44:03 +08:00
    不懂 php,不过搜了一下看 php 5.3 开始就有能回收环的 gc 了,为啥这个代码会内存泄漏?
    JJstyle
        7
    JJstyle  
    OP
       2020-08-13 19:45:29 +08:00 via iPhone
    @wangritian 是一段脚本,在 cli 下运行的,其实我只是讨论一个方法,毕竟我可以发现当内存达到一定量时退出,rnhou 让 supervisor 帮我重启
    JJstyle
        8
    JJstyle  
    OP
       2020-08-13 19:46:11 +08:00 via iPhone
    @JJstyle #7 rnhou => 然后
    JJstyle
        9
    JJstyle  
    OP
       2020-08-13 20:09:42 +08:00
    @cholerae 我看了一下 php 的文档,大概是 php5.3 后,只有当根缓冲区满了才会 清除循环引用产生的垃圾
    superwhite
        10
    superwhite  
       2020-08-13 20:21:06 +08:00
    php7.2 win,PHP7.3 centOs,试了你的代码,不会内存泄露
    CismonX
        11
    CismonX  
       2020-08-13 21:31:06 +08:00
    在 PHP 的 debug build 中,PHP 进程退出时 Zend MM 会打印出未释放的内存的相关信息(仅限那些用 emalloc 系列函数分配的由 Zend MM 管理的内存),包括其地址、分配这块内存的相关函数等

    当然,实际调试的时候这些信息可能不够,需要 valgrind 。
    wangbenjun5
        12
    wangbenjun5  
       2020-08-13 21:33:01 +08:00
    @wangritian 正解,除非写常驻进程的应用,不然不用操心内存泄露问题,如果要写常驻进程应用,为何不用 go
    JJstyle
        13
    JJstyle  
    OP
       2020-08-14 01:30:39 +08:00
    @wangbenjun5 帮朋友维护一个个人项目,恰好我对 php 熟悉
    zjsxwc
        14
    zjsxwc  
       2020-08-14 08:23:09 +08:00
    看来只能手动 gc 了

    1 <?php
    2
    3
    4 function fun1()
    5 {
    6 $a = ['hello'];
    7 $a[] = &$a;
    8 }
    9
    10 while(true) {
    11 fun1();
    12 gc_collect_cycles();
    13 echo memory_get_usage() . "\n";
    14 sleep(1);
    15 }
    zjsxwc
        15
    zjsxwc  
       2020-08-14 08:40:24 +08:00
    php 碰到内存“泄露”问题,有几个方面
    1. “全局变量” 或者 “常驻依赖注入容器对象” 的膨胀导致内存不够,比如 orm 中 EntityManager 没有及时 clear 追踪的不再被使用的数据库实体对象。
    2. 代码逻辑“死循环” 导致内存不够。
    3. 使用了存在内存泄露 bug 的 c/c++拓展。


    当接楼上我的回答,当 php 内存达到设置值时,比如 32M 时会自动触发垃圾回收,所以没有必要手动 gc_collect_cycles();


    php 7.3 之后有个 gc_status();可以看 gc 状态


    ```
    zjsxwc@zjsxwc-PC:~$ cat 2.php
    <?php


    function fun1()
    {
    $a = ['hello'];
    $a[] = &$a;
    }

    while(true) {
    var_dump(gc_status());
    fun1();
    var_dump(gc_status());
    gc_collect_cycles();
    echo memory_get_usage() . "\n";
    sleep(1);
    }
    zjsxwc@zjsxwc-PC:~$ '/home/zjsxwc/php74/bin/php' 2.php
    array(4) {
    ["runs"]=>
    int(0)
    ["collected"]=>
    int(0)
    ["threshold"]=>
    int(10001)
    ["roots"]=>
    int(0)
    }
    array(4) {
    ["runs"]=>
    int(0)
    ["collected"]=>
    int(0)
    ["threshold"]=>
    int(10001)
    ["roots"]=>
    int(1)
    }
    365496
    array(4) {
    ["runs"]=>
    int(1)
    ["collected"]=>
    int(1)
    ["threshold"]=>
    int(10001)
    ["roots"]=>
    int(0)
    }
    array(4) {
    ["runs"]=>
    int(1)
    ["collected"]=>
    int(1)
    ["threshold"]=>
    int(10001)
    ["roots"]=>
    int(1)
    }
    365496

    ```
    wangritian
        16
    wangritian  
       2020-08-14 09:34:53 +08:00
    @wangbenjun5 +1 我也是 web 项目用 php-swoole,小工具或者中间件用 go/python
    gouchaoer2
        17
    gouchaoer2  
       2020-08-14 11:08:03 +08:00
    php 有检测循环引用的机制,只是内存涨到一定大小才会触发
    jimduan
        18
    jimduan  
       2020-08-14 18:19:31 +08:00
    php5.3 后的 gc, 已经自动帮我们解决了!
    只需要更多的关注, 业务场景中是否一次性拉取过多的数据进行计算导致 OOM ;常驻内存脚本是否请求了很多资源没有释放


    PHP 的垃圾回收机制-理解 PHP 如何解决循环引用导致的内存泄漏问题
    http://blog.100dos.com/2017/04/07/php-garbage-collection-collect-cycles/

    请手动释放你的资源(Please release resources manually)
    https://www.laruence.com/2012/07/25/2662.html
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1834 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 25ms · UTC 16:28 · PVG 00:28 · LAX 08:28 · JFK 11:28
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.