深入理解PHP内存管理之谁动了我的内存

标签: PHP应用 PHP源码分析 memory_get_usage PHP php原理 | 发表时间:2011-03-04 11:21 | 作者:雪候鸟 flogliu
出处:http://www.laruence.com

首先让我们看一个问题: 如下代码的输出,

var_dump(memory_get_usage());
$a = "laruence";
var_dump(memory_get_usage());
unset($a);
var_dump(memory_get_usage());

输出(在我的个人电脑上, 可能会因为系统,PHP版本,载入的扩展不同而不同):

int(90440)
int(90640)
int(90472)

注意到 90472-90440=32, 于是就有了各种的结论, 有的人说PHP的unset并不真正释放内存, 有的说, PHP的unset只是在释放大变量(大量字符串, 大数组)的时候才会真正free内存, 更有人说, 在PHP层面讨论内存是没有意义的.

那么, 到底unset会不会释放内存? 这32个字节跑哪里去了?

要回答这个问题, 我将从俩个方面入手:

这32个字节去哪里了

首先我们要打破一个思维: PHP不像C语言那样, 只有你显示的调用内存分配相关API才会有内存的分配.
也就是说, 在PHP中, 有很多我们看不到的内存分配过程.
比如对于:

$a = "laruence";

隐式的内存分配点就有:

1. 为变量名分配内存, 存入符号表
2. 为变量值分配内存

所以, 不能只看表象.
第二, 别怀疑,PHP的unset确实会释放内存(当然, 还要结合引用和计数, 这部分的内容请参看我之前的文章深入理解PHP原理之变量分离/引用), 但这个释放不是C编程意义上的释放, 不是交回给OS.
对于PHP来说, 它自身提供了一套和C语言对内存分配相似的内存管理API:

emalloc(size_t size);
efree(void *ptr);
ecalloc(size_t nmemb, size_t size);
erealloc(void *ptr, size_t size);
estrdup(const char *s);
estrndup(const char *s, unsigned int length);

这些API和C的API意义对应, 在PHP内部都是通过这些API来管理内存的.

当我们调用emalloc申请内存的时候, PHP并不是简单的向OS要内存, 而是会像OS要一个大块的内存, 然后把其中的一块分配给申请者, 这样当再有逻辑来申请内存的时候, 就不再需要向OS申请内存了, 避免了频繁的系统调用.

比如如下的例子:

<?php
var_dump(memory_get_usage(TRUE)); //注意获取的是real_size
$a = "laruence";
var_dump(memory_get_usage(TRUE));
unset($a);
var_dump(memory_get_usage(TRUE));

输出:

int(262144)
int(262144)
int(262144)

也就是我们在定义变量$a的时候, PHP并没有向系统申请新内存.

同样的, 在我们调用efree释放内存的时候, PHP也不会把内存还给OS, 而会把这块内存, 归入自己维护的空闲内存列表. 而对于小块内存来说, 更可能的是, 把它放到内存缓存列表中去(后记, 某些版本的PHP, 比如我验证过的PHP5.2.4, 5.2.6, 5.2.8, 在调用get_memory_usage()的时候, 不会减去内存缓存列表中的可用内存块大小, 导致看起来, unset以后内存不变, 见评论).

现在让我来回答这32个字节跑哪里去了, 就向我刚才说的, 很多内存分配的过程不是显式的, 看了下面的代码你就明白了:

<?php
var_dump("I am Laruence, From http://www.laruence.com");
var_dump(memory_get_usage());
$a = "laruence";
var_dump(memory_get_usage());
unset($a);
var_dump(memory_get_usage());

输出:

string(43) "I am Laruence, From http://www.laruence.com"
int(90808) //赋值前
int(90976)
int(90808) //是的, 内存正常释放了

90808-90808 = 0, 正常了, 也就是说这32个字节是被输出函数给占用了(严格来说, 是被输出的Header占用了)

只增不减的数组

Hashtable是PHP的核心结构(了解Hashtable, 可以参看我之前的文章深入理解PHP之数组(遍历顺序)), 数组也是用她来表示的, 而符号表也是一种关联数组, 对于如下代码:

var_dump("I am Laruence, From http://www.laruence.com");
var_dump(memory_get_usage());
$array = array_fill(1, 100, "laruence");
foreach ($array as $key => $value) {
    ${$value . $key} = NULL;
}
var_dump(memory_get_usage());
foreach ($array as $key=> $value) {
    unset(${$value . $key});
}
var_dump(memory_get_usage());

我们定义了100个变量, 然后又按个Unset了他们, 来看看输出:

string(43) "I am Laruence, From http://www.laruence.com"
int(93560)
int(118848)
int(104448)

Wow, 怎么少了这么多内存?
这是因为对于Hashtable来说, 定义它的时候, 不可能一次性分配足够多的内存块, 来保存未知个数的元素, 所以PHP会在初始化的时候, 只是分配一小部分内存块给HashTable, 当不够用的时候再RESIZE扩容,

而Hashtable, 只能扩容, 不会减少, 对于上面的例子, 当我们存入100个变量的时候, 符号表不够用了, 做了一次扩容, 而当我们依次unset掉这100个变量以后, 变量占用的内存是释放了(118848 – 104448), 但是符号表并没有缩小, 所以这些少的内存是被符号表本身占去了…

现在, 你是不是对PHP的内存管理有了一个初步的认识了呢?


Comments

  • 2011/03/04, 飞晏 writes: 太详细了,“锱铢必较”的程序员才是真正大牛,建议出书。
  • 2011/03/04, 飞晏 writes: 以前C语言虽然学的不错,但是大二大三开始都开始接触高级语言并且被填鸭式的接受。现在看了你的很多文章发现如果我走PHP路线的话还是得把C语言搞的很熟~。很受启发!
  • 2011/03/04, gsid writes: 同上,支持出书!
  • 2011/03/04, horseluke writes: 话说,我更加看重CPU占用多一点,可惜只能从xhprof的cpu时间里面来猜算......我的思路对否?又或者,如何才能正确分析php的cpu占用问题呢......
  • 2011/03/04, 雪候鸟 writes: @horseluke getrusage — Gets the current resource usages 说明 array getrusage ([ int $who = 0 ] ) This is an interface to getrusage(2). It gets data returned from the system call.
  • 2011/03/04, maker writes: 精彩,这部分东西还真是第一次接触到,希望博主写更多的好文。
  • 2011/03/04, horseluke writes: @雪候鸟 多谢,我研究一下
  • 2011/03/04, shiny writes: 和坛子里的某个人形成鲜明对比。
  • 2011/03/04, 蜡烛公爵 writes: 试验了,结果如下: var_dump(memory_get_usage()); $a = "laruence"; var_dump(memory_get_usage()); unset($a); var_dump(memory_get_usage()); *************结果******************* int(55784) int(55888) int(55888) unset之前和之后没变 var_dump(memory_get_usage()); $a = ""; var_dump(memory_get_usage()); unset($a); var_dump(memory_get_usage()); ***********结果******************** int(55776) int(55880) int(55896) unset之后比unset之前占用的还多 这是何原因?如果说32字节各个系统各异,但unset完内存占用不但不降反而多出来了,这怎么解释?
  • 2011/03/04, 雪候鸟 writes: @蜡烛公爵 这个可能和你使用的PHP版本也有关系, PHP的某些版本, 在调用get_memory_usage的时候, 不会减去cache中的内存块size. 比如php5.2.4, php5.3.0rc1
  • 2011/03/04, 蜡烛公爵 writes: @雪候鸟 多谢指教
  • 2011/03/04, bjmayor writes: 鸟哥的文章越写越好了,我辈楷模呀。
  • 2011/03/04, zhou writes: 话说...前年看过你的blog了... 就是"年二十有七" 怎么现在还是哇... 哈哈哈
  • 2011/03/05, 雪候鸟 writes: @zhou 改了,不过是二十有八了,,, :)
  • 2011/03/06, iterse's blog writes: 长见识了,谢谢分享!
  • 2011/03/20, 谁动了我的内存(PHP内存管理) | 万维网黑客联盟 writes: [...] 本文地址: http://www.laruence.com/2011/03/04/1894.html [...]
  • 2011/04/04, reiko writes: 写的太好了。又长见识了。
  • 2011/05/24, wclssdn writes: 那$var = null; 和unset($var); 有什么区别么? 如果说. 对PHP自身的内存管理来说. 是不是unset($var);会好一些呢? 是不是变量表中已经把$var这个变量干掉了? 而$var = null; 还是会在变量表中存在$var 这个变量? 还有就是.. PHP肯定不会释放内存给OS是么?
  • 2011/05/25, 雪候鸟 writes: @wclssdn 是的, 对于"PHP肯定不会释放内存给OS是么?", 答案是不是的, 一块内存segment使用完毕以后, 内存管理器关闭以后,都会返回给OS的
  • 2011/05/25, wclssdn writes: 那 内存管理器 可能在某个PHP文件未执行完的时候提前关闭么? 我的意思是说.. 某个PHP文件,开始执行.. 一直申请内存. 直到整个PHP文件中的所有代码都执行完. PHP进程退出.. 才把内存还给OS是么?
  • 2011/05/25, 雪候鸟 writes: @wclssdn 一般你不用关心这个问题,不过既然你问了,我举个例子, 比如你申请向PHP申请1个字节的内存, PHP可能会向OS申请一大块内存, 比如4K的, 当你归还这1字节的内存的时候, PHP发现这块4k的内存, 没有其他地方再使用了, 就会把这块内存交回给OS, 当然, 一般情况下, 这个概率很小, 因为还存在一个PHP内存Cache的机制.
  • 2011/05/27, wclssdn writes: 谢谢解答~~~
  • 2011/05/29, cloud writes: int(84168) int(84336) int(84168) 我刚刚测试的结果 这个可能其他原因引起的吧

Copyright © 2010 风雪之隅 版权所有, 转载务必注明. 该Feed只供个人使用, 禁止未注明的转载或商业应用. 非法应用的, 一切法律后果自负. 如有问题, 可发E-mail至my at laruence.com.(Digital Fingerprint: 73540ba0a1738d7d07d4b6038d5615e2)

Related Posts:

相关 [理解 php 内存管理] 推荐:

深入理解PHP内存管理之谁动了我的内存

- flogliu - 风雪之隅
作者: Laruence(. 本文地址: http://www.laruence.com/2011/03/04/1894.html. 首先让我们看一个问题: 如下代码的输出,. 输出(在我的个人电脑上, 可能会因为系统,PHP版本,载入的扩展不同而不同):. 注意到 90472-90440=32, 于是就有了各种的结论, 有的人说PHP的unset并不真正释放内存, 有的说, PHP的unset只是在释放大变量(大量字符串, 大数组)的时候才会真正free内存, 更有人说, 在PHP层面讨论内存是没有意义的..

Android内存管理

- - CSDN博客推荐文章
首先Android内存管理机制相当复杂,想要讲清楚比较困难;其次对于绝大多数用户来说,只关心内存够不够用,至于内存如何管理的这种技术细节,不是用户需要去考虑的,写这样一个专题有没有意义. 毕竟我们是用手机,不是来研究手机的. 最后的顾虑是这个专题会不会太技术化了,绝大部分用户不会看或者说缺乏相应的背景.

Sun JDK 1.6内存管理

- 小丑鱼 - 淘宝JAVA中间件团队博客
分为使用篇、调优篇和实现篇三个部分,使用篇为填鸭式,调优篇为pattern式,实现篇为启发式,三个PPT的目标为:. 1.掌握Sun JDK的内存区域的划分;. 2.掌握Sun JDK垃圾收集器的使用方法和触发时机;. 4.掌握一些基本的GC调优的方法;. 5.了解自动内存管理的常见实现方法,以及Sun JDK所做的优化.

Android内存管理之道

- - CSDN博客移动开发推荐文章
相信一步步走过来的Android从业者,每个人都会遇到OOM的情况. 如何避免和防范OOM的出现,对于每一个程序员来说确实是一门必不可少的能力. 今天我们就谈谈在Android平台下内存的管理之道,开始今天的主题之前,先再次回顾两个概念. 内存泄漏:对象在内存heap堆中中分配的空间,当不再使用或没有引用指向的情况下,仍不能被GC正常回收的情况.

c++之内存管理

- - CSDN博客推荐文章
c++使用3种不同解决方案存储数据,区别是数据保留在内存中的时间. 两种存储持续性为自动:自动变量和寄存器变量(register没有内存地址)(堆栈). 在函数外定义的变量和使用关键字static定义的变量的存储持续性都为静态.. 外部链接性,内部链接性和无链接性. 所有静态变量都有下面的两个初始化特征:.

[译] HotSpot JVM 内存管理

- - IT瘾-dev
HotSpot JVM 内存管理. 更新时间:2018-03-28. 关于 JVM 内存管理或者说垃圾收集,大家可能看过很多的文章了,笔者准备给大家总结下. 这算是系列的第一篇,接下来一段时间会持续更新. 本文主要是翻译《 Memory Management in the Java HotSpot Virtual Machine》白皮书的前四章内容,这是 2006 的老文章了,当年发布这篇文章的还是 Sun Microsystems,以后应该会越来越少人记得这家曾经无比伟大的公司了.

PHP导出excel

- syeye - scofield PHP开发-SEO SEM
最近做一个项目,其中涉及到了数据导成excel的功能. 后来使用了 开源的 PHPExcel  http://phpexcel.codeplex.com/ 目前最新版是1.7.6. PHPExcel 可以生成 .xls 和 .xlsx (office2007). 比如设置 excel的title,keywords,description.

PHP框架 Yaf

- Le - 开源中国社区最新软件
Yaf是一个C语言编写的PHP框架,Yaf 的特点: 用C语言开发的PHP框架, 相比原生的PHP, 几乎不会带来额外的性能开销. 所有的框架类, 不需要编译, 在PHP启动的时候加载, 并常驻内存. 更短的内存周转周期, 提高内存利用率, 降低内存占用率. 支持全局和局部两种加载规则, 方便类库共享.

Memcached内存管理机制浅析

- 圣斌 - basic coder
Memcached的内存管理在网上也可以搜集到不少不错的文章,新浪的这篇《Memcached深度分析》讲得不错,读别人的文章还是不如自己直接去读源码分析源码来得直接,这里写一下我阅读Memcached源码时对于Memcached内存管理机制的理解. Memcached的代码结构很简单,从main()函数入口进去之后便是几个模块的初始化函数,和内存管理相关的主要有两个函数,一个是assoc_init(),这个是用来初始化哈希表的,关于这个哈希表的作用留在外面讨论,另一个是slabs_init(),该函数用来初始化slab,下面先来讨论一下slab机制.

JVM内存管理学习总结(一)

- - CSDN博客互联网推荐文章
I.JVM进程的生命周期. JVM实例的生命周期和java程序的生命周期保持一致,即一个新的程序启动则产生一个新的JVM进程实例,程序结束则JVM进程实例伴随着消失. 那么程序启动和程序终止就是JVM实例生命周期的两个边界,两个边界点可以这么理解:一个拥有程序入口(main函数)的class在执行main方法时,相应的JVM就被创建了(即JVM生命周期的起点),当由此main函数启动的所有非守护线程都终止时,JVM即退出(JVM实例生命周期的终点).