我从来不认为这个问题是个问题, 直到昨天.

昨天晚上的时候, 我提交了一个RFC, 关于引入finally到PHP, 实现这个功能的出发点很简单, 因为我看见不少人的需求, 另外还有就是Stas说, 一直只看到讨论, 没看到有人实现. 于是我就给实现了.

发到邮件组以后, 一个开发组的同学Nikita Popov(nikic), 表示强烈反对这个RFC, 当然最初的论点他说了很多, 最后我们在线讨论的时候, 他表达了一个他的观点:

“PHP在请求结束后会释放所有的资源, 所以我们没有必要调用fclose,或者mysql_close来释放资源, PHP会替我们做”

并且他表示, 他从来都不会调用fclose, 认为fclose的存在只是为了继承C函数族.

我很惊讶, 我也不知道还有多少人是和他一样的想法, 所以我决定写这篇文章.

在PHP5.2以前, PHP使用引用计数(Reference count)来做资源管理, 当一个zval的引用计数为0的时候, 它就会被释放. 虽然存在循环引用(Cycle reference), 但这样的设计对于开发Web脚本来说, 没什么问题, 因为Web脚本的特点和它追求的目标就是执行时间短, 不会长期运行. 对于循环引用造成的资源泄露, 会在请求结束时释放掉. 也就是说, 请求结束时释放资源, 是一种部补救措施(backup).

然而, 随着PHP被越来越多的人使用, 就有很多人在一些后台脚本使用PHP, 这些脚本的特点是长期运行, 如果存在循环引用, 导致引用计数无法及时释放不用的资源, 则这个脚本最终会内存耗尽退出.

所以在PHP5.3以后, 我们引入了GC, 也就是说, 我们引入GC是为了解决用户无法解决的问题.

这个是历史, 我简单介绍下, 现在让我们回头来看开头的问题, 是不是因为PHP会在请求结束后释放所有的资源, 于是我们就可以不用手动释放呢?

看一个例子:

Mysql最大连接数(mysql.max_connections)

  1. <?php
  2. $db = mysql_connect() ;
  3. $resut = mysql_query();
  4. // process result...
  5. usleep(500);
  6. //mysql_close($db); let's say, you didn't call to this
  7. // other logic, assuming it costs 5s
  8. sleep(5);
  9. exit(0); //finish

上面的例子, 我们会保持一个和Mysql的连接5秒钟, 这样的脚本对于一般的应用来说没有关系, 但是对于一个请求量很大的脚本来说, 会导致一个致命问题:

比如一个繁忙的应用, 每秒要处理来自用户的1000个请求, 那么5秒钟请求多少个? 5 * 1000 = 5000, 而Mysql有最大连接数限制(mysql.max_connections), 这个数字一般不超过2000, 默认的会更低:(mysql.max_connections),

那么, 这样代码会导致你的应用, 根本无法正常提供服务. 而如果我们在对Mysql的处理完成后就关闭这个连接, 那么就不会触发这个问题.

而我们在实践中, 遇到过一个更加实际的问题, 看下面的例子:

  1. <?PHP
  2. $mmc = new Memcached();
  3. $mysql = mysql_connect();
  4. //process
  5. mysql_close($mysql);
  6. $mmc->close();

这是一个真实的教训, 代码如上面所示, 突然有一天我们的Mysql出现了问题, 导致连接Mysql的耗时增大, 然后就导致, 一个脚本对Memcached连接占用过长, 最后Memcache因为连接数太多, 就拒绝服务了..

所以, 我们一定要让连接代价最高的资源, 最先初始化.

系统最大句柄 (/proc/sys/fs/file-max)

这个很简单, 如果你持续打开句柄, 而不释放, 那么你有可能触发系统最大句柄限制, 对于进程来说, 自己还有进程可打开句柄数限制(ulimit -n).

系统调用是昂贵的(System call is expensive)

PHP之所以会在请求结束后正确的释放掉所有的资源, 内存, 这是因为当我们在脚本中使用新的内存的时候, PHP会向OS申请一大块内存(ZEND_MM_SEG_SIZE大小), 然后分给你你需要的合适的一块小内存.

当你不使用这块小内存的时候, PHP也不会返还给OS, 而是保留下来给后续的处理使用.

我们知道, malloc(3)会导致系统调用(brk(2))(当然也可能是mmap, 我们此处不考虑这个细节, thanks to 华裔), 而系统调用是昂贵的.

所以, 如果你使用完了资源不及时释放, 那么后续的逻辑如果请求内存, PHP发现之前申请的一大块内存已经分光了, 它就只好再次向OS发起malloc调用, 得到一块新的大内存. 并且它还需要对这个大内存做一些标记处理..

而如果你使用完资源, 及时释放的话, 那么下次脚本申请内存的时候, 你之前归还的内存块就可以被重复利用, 那么也许你的整个脚本只需要和OS申请一次内存.

内存峰值(Memory peak usage)

这个和上面的有一定的关系, 当你使用完资源就释放, 然后后续又使用这样的资源. 那么PHP的内存占用会是:

资源+1 -> 资源-1 -> 资源+1 -> 资源-1 (峰值是1)

而如果你是等到PHP请求结束再释放:

资源+1 -> 资源 + 1 …. -> 资源 -1 -> 资源 – 1 (峰值是2)

也就说, 一个良好的编写的脚本可能要比一个瞎写的脚本, 要省很多峰值内存..

考虑一个极端情况, 对一个很繁忙的服务器来说, 比如有10个PHP进程, 每个PHP进程最大1G内存, 而服务器只有8G内存.

结论 (conclusion)

结论很明显, 我开头也说过了, 我从来不认为这个是个问题.

这里说一句, 如果你买了一本PHP的书, 它告诉你: “不用在PHP主动释放资源, 因为PHP会帮你释放”的话, 我建议你, 烧了它.

请手动释放你的资源(Please release resources manually)的更多相关文章

  1. [转]在C#中使用托管资源和非托管资源的区别,以及怎样手动释放非托管资源:

    托管资源指的是.NET可以自动进行回收的资源,主要是指托管堆上分配的内存资源.托管资源的回收工作是不需要人工干预的,有.NET运行库在合适调用垃圾回收器进行回收. 非托管资源指的是.NET不知道如何回 ...

  2. 5.C#释放非托管资源1

    释放非托管资源 在介绍释放非托管资源的时候,我觉得有必要先来认识一下啥叫非托管资源,既然有非托管资源,肯定有托管资源. 托管资源指的是.net可以自棕进行回收的资源,主要是指托管堆上分配的内存资源.托 ...

  3. c++继承构造子类调用父类构造函数的问题及关于容器指针的问题及当容器里储存指针时,记得要手动释放

    看下面的一个问题: class Person { private: string name; public: Person(const string& s=""){ nam ...

  4. C#编程(七十四)----------释放非托管资源

    释放非托管资源 在介绍释放非托管资源的时候,我觉得有必要先来认识一下啥叫非托管资源,既然有非托管资源,肯定有托管资源. 托管资源指的是.net可以自棕进行回收的资源,主要是指托管堆上分配的内存资源.托 ...

  5. Dispose模式释放非托管资源

    实现方式用的是设计模式里的模板模式,基类先搭好框架,子类重写void Dispose(bool disposing) 即可. 需要注意的是基类的Finalize函数也就是析构函数调用的是虚函数void ...

  6. Linux手动释放缓存的方法

    Linux释放内存的命令:syncecho 1 > /proc/sys/vm/drop_caches drop_caches的值可以是0-3之间的数字,代表不同的含义:0:不释放(系统默认值)1 ...

  7. Linux内存(手动释放cache)

    项目的扩容申请了一台机器,到手之后看一下机器的指标,看到内存使用情况是这样的. 1.查看内存 free $ free -h total used free shared buffers cached ...

  8. linux如何手动释放linux内存

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

  9. AltiumDesigner画图不求人11 | 提高AD20启动速度的方法七选择手动释放工程 | 视频教程 | 你问我答

    往期文章目录 AD画图不求人1 | AD20软件安装视频教程 | 含软件安装包 AD画图不求人2 | 中英文版本切换 AD画图不求人3 | 高亮模式设置 AD画图不求人4 | 双击设计文件无法启动Al ...

随机推荐

  1. myeclipse如何设置或关闭断点调试自动跳入debug模式

    遇到了很坑的问题,在myeclipse(eclipse应该也一样)开发过程中,打了断点调试,最初时候会弹出一个弹出框,让你选择是否进入debug模式,结果一不小心点了一个记住选择,然后选择了yes,结 ...

  2. 将make的输出重定向到文件(转)

    原文:将make的输出重定向到文件 [用法] 1.想要把make输出的全部信息,输出到某个文件中,最常见的办法就是:make xxx > build_output.txt此时默认情况是没有改变2 ...

  3. Spring + Shiro 项目 + HttpSessionListener 【调用springService问题】&【Session失效问题】

    功能描述: 当用户退出(主动)或者关闭浏览器(session超时)的时候,利用本次登录Ip更新上次登录IP.有人可能要问,你在用户登录的时候记录不就行了.可是我有两个字段,一个为本次登录IP,另外一个 ...

  4. java代码示例(6-3)

    创建AdministratorTest.java /** * 需求分析:创建管理员对象 * @author chenyanlong * 日期:2017/10/15 */ package com.hp. ...

  5. Window配置环境变量

    拿Java为例,讲一下怎么配置环境变量使得javac可以在全局被调用: 1.添加环境变量 例如我们的Java下载在了:D:\Java\v1.8 我们就到环境变量那添加一个例如叫“Java_path”的 ...

  6. Hadoop生态圈-Oozie实战之调度shell脚本

    Hadoop生态圈-Oozie实战之调度shell脚本 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 本篇博客展示案例:使用Oozie调度Shell脚本. 1>.解压官方案例 ...

  7. CM记录-Hadoop 分布式文件系统HDFS(登录、配置、监控)

    1.登录(浏览器输入ip地址:7180,登录用户名和登录密码即可) 2.CM主界面(各个组件,监控图表,绿色代表运行正常.黄色代表运行不良,需要关注根据实际情况调整,红色代表故障,需要排查问题) 3. ...

  8. typealias

    类的别名

  9. 【转】LR分析法

    转自:http://guanjy0129.blog.163.com/blog/static/1115494452010614113333509/ LR分析法的归约过程是规范推导的逆过程,所以LR分析过 ...

  10. Quartz.net创建windows服务

    序言 安装服务 sc create XXService binpath= "XXService.exe" start= auto sc description XXService ...