解决php内存泄露问题

标签: php 内存泄露 问题 | 发表时间:2013-03-11 12:40 | 作者:con@lutaf.com (鲁塔弗)
出处:http://lutaf.com

背景

这是08年写的一份文档,我当时在一家网站刚接手做技术负责人,网站每天大概有60万ip/300万pv的访问,网站产品很复杂,代码结构差,开发工程师来来去去,代码只能只读了。突然有一天开始频繁出现php-fpm进程耗光内存和cpu占有率飙升,前端频繁出现504错误

php-fpm进程耗光内存 这个就是传说中的内存泄露,所谓内存泄露,是指进程在运行过程中,内存占用率逐步上升而不释放,导致系统可用内存越来越少的情况

严格上说,这个也不算致命错误,“内存泄露”只对长期运行的程序有威胁,对单一任务的执行脚本不需要担心

最简单的处理方式,是定时重启进程。php-fpm的配置信息里面有个max_request,就定义一个fastcgi进程处理完多少个请求之后退出这样系统可用释放掉内存,但是如果内存占用率增长速度非常快,频繁重启进程,就会影响服务的稳定性,所以这个问题必须正面解决

内存泄露排查非常困难

  • 因为代码规模非常大,想靠做code review的方式来查基本上不可能
  • php并非运行在虚拟机上,没有什么官方的monitor(类似java hprof,JVM Monitor等)
  • 在互联网上搜索,找不到任何答案

探索解决问题

  1. 使用 valgrind 调试php-cgi进程

    Valgrind 是一个linux常用的程序的内存调试和代码剖析,对调试C/C++程序的内存泄露很有帮助,它的机制是在系统alloc/free等函数调用上加计数。用 valgrind 调试php-cgi,要求php-cgi 是debug版本,实践证明行不通:

    1. php-cgi debug 版本放在线上根本跑不起来,性能太差 2. php程序的内存泄露,是由于一些循环引用,或者gc的逻辑错误,valgrind无法探测,它适合去检查php解释器是否有内存泄露问题
  2. php解释器(Zend core)自带有检查内存泄露的机制

    php解释器的核心代码叫做(Zend Core) 在用valgrind 调试php-cgi进程,我查看了php-cgi的代码,发现zend core 实现了内存泄露的自我检查 但是 同上原因,php-cgi debug 跑不起来,也无法得到调试信息

  3. FreeBSD 的 DTrace

    DTrace是freebsd 系统支持的核心调试器,可以在各个系统函数调用上加计数点,twitter曾经用过。这个方法最后没有使用 有如下原因:

    1. 需要找一台服务器安装freebsd,并部署到线上、或者模拟负载,非常繁琐

    2. 我仔细研究了DTrace的文档,发现这个可以认为是增强的 valgrind,也不能解决我们的问题

这3种方法都不行,陷入困境.但是换个角度思考:虽然解决php程序内存泄露没有方便的工具,但是 web 程序是按请求切分的,一个http请求,对应的php进程执行一个php文件

一个自然的想法是,记录 每次 http请求处理前后php进程的内存占用率之差,然后对结果排序,就能找出,让进程内存增加可能性最大的 文件 ,这个文件导致内存泄露的可能性最大

计算进程内存占用率有两种方式

  1. php内置函数 memory_get_usage

    这个函数是 Zend Core里面一个计数器,是zend core认为的内存使用量,但是php内存泄露有可能是zend core逻辑错误导致的,所以memory_get_usage不一定可靠

  2. linux 系统文件 /proc/{$pid}/status 会记录某个进程的运行状态,里面的 VmRSS 字段记录了该进程使用的常驻物理内存(Residence),这个就是该进程实际占用的物理内存了,用这个数据比较靠谱,在程序里面提取这个值也很容易

找到思路,就可以开始动手写程序

  1. 直接修改了php-cgi的源代码,在main.c里面处理每个fastcgi请求前后分别加计数代码,输出日志到log文件,重新编译上线

    运行30分钟之后,执行

    cat short.log| awk '{print $3 "\t" $7 "\t" $6 "\t" $4$5}' |sort -r -n |head -n 100

    很容易找到最可能出现内存泄露的代码文件,然后进一步排查,重构代码,这就很简单了:能不加载的文件就不加载,大数组用完之后赶紧unset ....

    搞定!

后面的故事

后来,我才发现其实不需要去修改php的源代码,php.ini配置文件里面有两个配置项: auto_append_file,auto_prepend_file,可以在请求前后注入代码 ....

真是悲剧

web程序做性能优化也是这个思路,但是要简单很多,无需写代码,在nginx log里面加上$request_time ,用awk/sort 处理一下就可以找出瓶颈。

相关 [php 内存泄露 问题] 推荐:

解决php内存泄露问题

- - 鲁塔弗的博客
这是08年写的一份文档,我当时在一家网站刚接手做技术负责人,网站每天大概有60万ip/300万pv的访问,网站产品很复杂,代码结构差,开发工程师来来去去,代码只能只读了. 突然有一天开始频繁出现php-fpm进程耗光内存和cpu占有率飙升,前端频繁出现504错误. php-fpm进程耗光内存 这个就是传说中的内存泄露,所谓内存泄露,是指进程在运行过程中,内存占用率逐步上升而不释放,导致系统可用内存越来越少的情况.

iOS开发那些事--性能优化–内存泄露问题的解决

- - ITeye博客
内存泄漏(Memory Leaks)是当一个对象或变量在使用完成后没有释放掉,这个对象一直占有着这块内存,直到应用停止. 如果这种对象过多内存就会耗尽,其它的应用就无法运行. 这个问题在C++、C和Objective-C的MRR中是比较普遍的问题. 在Objective-C中释放对象的内存是发送release和autorelease消息,它们都是可以将引用计数减1,当为引用计数为0时候,release消息会使对象立刻释放,autorelease消息会使对象放入内存释放池中延迟释放.

Android 性能优化之使用MAT分析内存泄露问题

- - CSDN博客推荐文章
转载请注明本文出自xiaanming的博客( http://blog.csdn.net/xiaanming/article/details/42396507),请尊重他人的辛勤劳动成果,谢谢. 内存泄露就像一个定时炸弹,随时都有可能使我们的应用程序崩溃掉,所以作为一名Android开发人员,还是需要有分析内存泄露的能力,说道这里我们还是要说下什么是内存泄露,内存泄露是指有个引用指向一个不再被使用的对象,导致该对象不会被垃圾回收器回收.

Java常见问题分析(内存溢出、内存泄露、线程阻塞等)

- - Java - 编程语言 - ITeye博客
Java垃圾回收机制(GC) . 堆内存3代分布(年轻代、老年代、持久代) . ML(内存泄露) OOM(内存溢出)问题现象及分析 . IBM DUMP分析工具使用介绍. Java应用CPU、线程问题分析. Java垃圾回收机制(GC). 1.GC机制作用 . 1.1 JVM自动检测和释放不再使用的对象内存 .

php初学者常见问题

- - SQL - 编程语言 - ITeye博客
最令PHP初学者头痛的十四个问题. 管理提醒: 本帖被 haowubai 执行置顶操作(2009-04-16) 【1】面之间无法传递变量 get,post,session在最新的php 版本中自动全局变量是关闭的,所以要从上一面取得提交过来得变量要使用$_GET[’foo’],$_POST[’foo’],$_SESSION[’foo’]来得到.

关于内存泄露

- - 银河里的星星
valgrind 详细说明  http://www.cnblogs.com/wangkangluo1/archive/2011/07/20/2111273.html. 近期Imgsrc一处内存泄露问题的查找和解决  http://rdc.taobao.com/blog/cs/?p=1651.

ios Instruments 内存泄露

- - ITeye博客
虽然iOS 5.0版本之后加入了ARC机制,由于相互引用关系比较复杂时,内存泄露还是可能存在. 这里讲述在没有ARC的情况下,如何使用Instruments来查找程序中的内存泄露,以及NSZombieEnabled设置的使用. 本文假设你已经比较熟悉Obj-C的内存管理机制. 实验的开发环境:XCode 4.5.2.

C++检查内存泄露

- - CSDN博客推荐文章
说明,我使用的ide是vs2008. 内存泄露的检测一般在debug模式下进行. 2.在需要检查内存泄露的cpp头部加上. 4.然后就可以在输出中看泄露情况了. 举个例子,例子中我用newEx表示的上述宏定义中的new. 输出中显示的内容(debug下运行程序,然后点叉叉关闭程序).   Data: <                > CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD .

Java的内存泄露

- - Java译站
Java有垃圾回收,因此不会出现内存泄露. 尽管Java的确有垃圾回收器来回收那些不用的内存块,但你不要指望它能够点铁成金. GC减轻了开发人员肩上的负担,而原本的那些工作非常容易出错,不过并不是所有内存分配的问题它都能够解决. 更糟糕的是,Java的设计允许它可以欺骗GC,使得它能够保留一些程序已经不再使用的内存.

ThreadLocal的内存泄露

- - zzm
ThreadLocal的目的就是为每一个使用ThreadLocal的线程都提供一个值,让该值和使用它的线程绑定,当然每一个线程都可以独立地改变它绑定的值. 如果需要隔离多个线程之间的共享冲突,可以使用ThreadLocal,这将极大地简化你的程序.. 关于的ThreadLocal更多内容,请参考《 ThreadLocal》.