折磨半个月终于找到php脚本所谓的“内存泄露”原因!

转自 https://blog.csdn.net/tao_627/article/details/9532497——跟我遇到的问题一样。

最近项目改版做2期,1期是我写的,当然2期也由我主要负责,我借鉴了同事和网上的多种可取方法,在性能上做了较大提升。

项目是一个后台网页爬取程序,使用php开发,基于DOMDocument+Xpath+curl多线程异步,同时少量网页使用phpQuery处理,正则表达式提取等等,处理数据的结果写入MySQL。在批量爬取处理过程中,内存占用很平稳,比如开300线程同时爬取html网页,做Dom+xpath提取,内存占用22m,一次性添加数万个任务进任务池,这些都没有问题,

但是在公网服务器测试过程中,发现top显示的used持续增加,free持续减少,运行一次这样的批量任务,几乎就要“耗尽”三分之一的物理内存,我一下急了,怀疑是php脚本有内存泄露。


我逐个排查了几个大的库,并做了单独的测试分析,curl多线程库,是以前工作中多次使用的,没有发现明显问题,使用30个url同时爬取的demo,发现前后内存使用正常,没有内存泄露的问题;于是我怀疑是否是dom+xpath的问题,后来使用百度搜国内的网页,没有发现有人反映dom的内存泄露问题,不甘心,又使用google*到国外找*之类的网站,找问题,有几个网页反映dom有内存泄露的问题,给出了解决方法,我欣喜若狂,赶紧修改代码,测试,发现问题依旧。


这个内存泄露的问题,困扰了半个月,每天都吃不好睡不香,我这才体会到当程序员的“苦逼”,特别是找不到解决问题的思路,又没有同行的指点交流,太痛苦了。没有办法,只有这样拖着,直到今天……


今晚依旧郁闷,但还是不死心,我想,我还是搞清楚“内存泄露”的特征或是基本表现吧?

我是通过Linux上top命令判断的,当然我就得从top命令入手,看mem那块的显示信息,要采用更专业的数据,最好还是使用free命令:

free 采用kb显示,你可以使用free -m采用MB为单位显示。

通过查阅资料,我弄清楚了程序占用内存,系统可用内存,物理内存,等等几个量的关系如下:

在命令行输入

 free
                     total       used           free     shared    buffers     cached
Mem:       4149156    4130412      18744          0      13220    2720160
-/+ buffers/cache:    1397032    2752124
Swap:      6289408        144    6289264

第1行

total 内存总数: 4149156
used 已经使用的内存数: 4130412
free 空闲的内存数: 18744
shared 当前已经废弃不用,总是0
buffers Buffer Cache内存数: 13220
cached Page Cache内存数: 2720160

关系:total = used + free

第2行:
-/+ buffers/cache的意思相当于:
-buffers/cache 的内存数:1397032 (等于第1行的 used - buffers - cached)
+buffers/cache 的内存数: 2752124 (等于第1行的 free + buffers + cached)

可见-buffers/cache反映的是被程序实实在在吃掉的内存,而+buffers/cache反映的是可以挪用的内存总数


下面是令我郁闷的top之前的截图

折磨半个月终于找到php脚本所谓的“内存泄露”原因!折磨半个月终于找到php脚本所谓的“内存泄露”原因!

令我郁闷是两项,used和cached的值太高了,而作为一个公网服务器,我还只是简单就跑了个php脚本,就把内存搞成这样,真是死了的心都用了。


下面的这篇文章给了我解决问题的希望:

http://blog.chinaunix.net/uid-7177878-id-126651.html

在此感谢这位朋友,我将它的文章贴出来:

  1. 细心的朋友会注意到,当你在Linux下频繁存取文件后,物理内存会很快被用光,当程序结束后,内存不会被正常释放,而是一直作为caching.这个问题,貌似有不少人在问,不过都没有看到有什么很好解决的办法.那么我来谈谈这个问题.

    先来说说free命令

    [[email protected] ~]# free -m
               total     used     free     shared     buffers     cached
    Mem:    249      163      86           0           10           94
    -/+ buffers/cache: 58    191
    Swap:   511        0        511

    其中:

    total 内存总数

    used 已经使用的内存数

    free 空闲的内存数

    shared 多个进程共享的内存总额

    buffers Buffer Cache和cached Page Cache 磁盘缓存的大小

    -buffers/cache 的内存数:used - buffers - cached

    +buffers/cache 的内存数:free + buffers + cached

    可用的memory=free memory+buffers+cached

    有了这个基础后,可以得知,我现在used为163MB,free为86,buffer和cached分别为10,94

    那么我们来看看,如果我执行复制文件,内存会发生什么变化.

    [[email protected] ~]# cp -r /etc ~/test/
    [[email protected] ~]# free -m
    total used free shared buffers cached
    Mem: 249 244 4 0 8 174
    -/+ buffers/cache: 62 187
    Swap: 511 0 511

    在我命令执行结束后,used为244MB,free为4MB,buffers为8MB,cached为174MB,天呐都被cached吃掉了.别紧张,这是为了提高文件读取效率的做法.

    为了提高磁盘存取效率, Linux做了一些精心的设计, 除了对dentry进行缓存(用于VFS,加速文件路径名到inode的转换), 还采取了两种主要Cache方式:Buffer Cache和Page Cache。前者针对磁盘块的读写,后者针对文件inode的读写。这些Cache有效缩短了 I/O系统调用(比如read,write,getdents)的时间。"

    那么有人说过段时间,linux会自动释放掉所用的内存,我们使用free再来试试,看看是否有释放>?

    [[email protected] test]# free -m
    total used free shared buffers cached
    Mem: 249 244 5 0 8 174
    -/+ buffers/cache: 61 188
    Swap: 511 0 511

    MS没有任何变化,那么我能否手动释放掉这些内存呢???回答是可以的!

    /proc是一个虚拟文件系统,我们可以通过对它的读写操作做为与kernel实体间进行通信的一种手段.也就是说可以通过修改/proc中的文件,来对当前kernel的行为做出调整.那么我们可以通过调整/proc/sys/vm/drop_caches来释放内存.操作如下:

    [[email protected] test]# cat /proc/sys/vm/drop_caches
    0
    首先,/proc/sys/vm/drop_caches的值,默认为0

    [[email protected] test]# sync

    手动执行sync命令(描述:sync 命令运行 sync 子例程。如果必须停止系统,则运行 sync 命令以确保文件系统的完整性。sync 命令将所有未写的系统缓冲区写到磁盘中,包含已修改的 i-node、已延迟的块 I/O 和读写映射文件)

    [[email protected] test]# echo 3 > /proc/sys/vm/drop_caches
    [[email protected] test]# cat /proc/sys/vm/drop_caches
    3

    将/proc/sys/vm/drop_caches值设为3

    [[email protected] test]# free -m
    total used free shared buffers cached
    Mem: 249 66 182 0 0 11
    -/+ buffers/cache: 55 194
    Swap: 511 0 511

    再来运行free命令,发现现在的used为66MB,free为182MB,buffers为0MB,cached为11MB.那么有效的释放了buffer和cache.

    有关/proc/sys/vm/drop_caches的用法在下面进行了说明

    /proc/sys/vm/drop_caches (since Linux 2.6.16)
    Writing to this file causes the kernel to drop clean caches,
    dentries and inodes from memory, causing that memory to become
    free.

    To free pagecache, use echo 1 > /proc/sys/vm/drop_caches; to
    free dentries and inodes, use echo 2 > /proc/sys/vm/drop_caches;
    to free pagecache, dentries and inodes, use echo 3 >
    /proc/sys/vm/drop_caches.

    Because this is a non-destructive operation and dirty objects
    are not freeable, the user should run sync first.
下面是另一个网友类似的经历,不过当时看到他的文章时,我还不了解命令调整bash脚本,处理一张图片后手动释放一下内存(sync && echo 3 > /proc/sys/vm/drop_caches),然后top看到的内存消耗就正常了。
这句话的含义,现在完全明白了,他的文章是
http://xiaoshenge.iteye.com/blog/1490230
再次将它的文章贴出来:

 

前言:持续我一贯的标题党作风,说说例子解决方案,没有深入探讨。

 

情景:线上图片服务压缩的图片品质(100),缩略图品质(100)占用了很多空间,导致后来又55个文件了(占用空间160G)才发现这个问题。现在需要解决的是把这部分压缩个低品质的缩略图节省空间(当然在这个硬盘白菜价的时代搞这样的问题没这个必要,我这里讨论的不是节省空间是想找出内存消耗问题)。我用php脚本重新生成缩略图的时候,通过top发现内存消耗一直增加导致后来脚本报错内存不够了,到底谁动了我的内存?

 

处理代码版本一(php):

 

Php代码  折磨半个月终于找到php脚本所谓的“内存泄露”原因!
  1. set_time_limit(0);  
  2.   
  3. function thumbnailimage($img,$width,$height,$savefile){  
  4.     $new_img = imagecreatetruecolor ( $width$height );  
  5.     imagedestroy($new_img);  
  6. }  
  7. //$list:是那个55万的文件名  
  8. foreache($list as $v) {  
  9.     $img = imagecreatefromjpeg($v);  
  10.     thumbnailimage($img,480,300,$savepath);  
  11.     imagedestroy($img);  
  12. }  
 

 

这个脚本处理了5千多个的时候,由于内存不够用挂了,然后我改了php.ini里面的memory_limit改成了5G,但是随着脚本的执行,内存也会被消耗殆尽。

于是我就以为是php的内存泄露了,然后就想用其他方案解决,在老王的技术手册里面看到GraphicsMagick这个工具,然后写了个脚本去处理,结果发现top看到的内存消耗还是一直增加,然后经人提示这个应该是系统操作文件(写文件)文件被缓存了消耗了内存,

调整bash脚本,处理一张图片后手动释放一下内存(sync && echo 3 > /proc/sys/vm/drop_caches),然后top看到的内存消耗就正常了。

由于这个bash脚本处理的速度还不如php的gd库处理,然后就换成php处理。

验证php脚本内存消耗的原因:

处理代码版本二(php):

 

Php代码  折磨半个月终于找到php脚本所谓的“内存泄露”原因!
  1. set_time_limit(0);  
  2.   
  3. function thumbnailimage($img,$width,$height,$savefile){  
  4. $new_img = imagecreatetruecolor ( $width$height );  
  5. ...  
  6. imagedestroy($new_img);  
  7. }  
  8. //$list:是那个55万的文件名  
  9. foreache($list as $v) {  
  10.     $memory1=memory_get_usage();  
  11.     file_put_contents('memory','memory1:'.$memory1."\n",FILE_APPEND);  
  12.       
  13.     $img = imagecreatefromjpeg($v);  
  14.     thumbnailimage($img,480,300,$savepath);  
  15.     imagedestroy($img);  
  16.       
  17.     $memory1=memory_get_usage();  
  18.     file_put_contents('memory','memory1:'.$memory1."\n",FILE_APPEND);  
  19.       
  20.     system('sync && echo 3 > /proc/sys/vm/drop_caches');  
  21. }  
 

通这个版本处理内存消耗就正常了,当然php进程也要消耗内存,php.ini的memory_limit稍微改大一下。

从这里看来消耗内存的是系统操作文件消耗的,不是php,由于我的无知一开始错怪了PHP。

 

结尾:这个例子只是简单的描述我找到内存消耗的原因。



我按照上面的指示做了一遍,果然见效,参见下面的截图。
折磨半个月终于找到php脚本所谓的“内存泄露”原因!


最后,我总结一下:

所谓的dom内存泄露或者其他php脚本的内存泄露,其实不是真正的内存泄露,因为php进程在运行时res显示项很平稳,对于used和cached的变化,那是Linux的内存管理机制在起作用。

再次感谢这两位朋友,让我解开了困扰许久的问题。