PHP的内存泄露问题与垃圾回收

你写了一个PHP脚本,一般都不用考虑内存泄露和垃圾回收的问题,因为一般情况下你的脚本很快就执行完退出了。

但在一些运行时间长,数据量大的时候,程序运行一段时间后,php脚本就占用了过多内存,然后就报错(PHP Fatal error: Allowed memory size of 134217728 bytes exhausted)退出了。一般来说,每个页面处理结束,新建的simple_html_dom对象就应该被销毁了——但是实际上没有,很明显,内存泄露发生了。

PHP的垃圾回收机制

PHP5.3之前使用的垃圾回收机制是单纯的“引用计数”,也就是每个内存对象都分配一个计数器,当内存对象被变量引用时,计数器+1;当变量引用撤掉后,计数器-1;当计数器=0时,表明内存对象没有被使用,该内存对象则进行销毁,垃圾回收完成。

“引用计数”存在问题,就是当两个或多个对象互相引用形成环状后,内存对象的计数器则不会消减为0;这时候,这一组内存对象已经没用了,但是不能回收,从而导致内存泄露。

PHP5.3开始,使用了新的垃圾回收机制,在引用计数基础上,实现了一种复杂的算法,来检测内存对象中引用环的存在,以避免内存泄露。

查看内存是否泄露

看是否有该释放的内存没有被释放,可以简单的通过 调用  memory_get_usage  函数查看内存使用情况来判断;memory_get_usage 函数返回的内存使用数据据说不是很准确,可以使用  php 的 xdebug 扩展来获得更准确翔实的内存使用情况。

class A{
    private $b;
    function __construct(){
        $this->b = new B($this);
    }
    function __destruct(){
        //echo "A destruct\n";
    }
}

class B{
    private $a;
    function __construct($a){
        $this->a = $a;
    }
    function __destruct(){
        //echo "B descturct\n";
    }
}

for($i=0;;$i++){
    $a = new A();
    if($i00 == 0){
        echo memory_get_usage()."\n";
    }
}

上面就构造了一个会产生环状引用的例子。每次创建一个A对象的实例a,a就创建一个B对象的实例b,同时让b引用a。这样,每个A对象永远被一个B引用,而每个B对象同时被一个对象A引用,引用环就这样产生了。

在PHP5.2的环境下执行这段代码,会发现内存使用在单调上涨,也没有A和B的析构函数被执行后输出的“A/B desctruct”信息;直到内存耗尽,输出“PHP Fatal error:  Allowed memory size of 134217728 bytes exhausted (tried to allocate 40 bytes)”。

在PHP5.3的环境下执行这段代码,则发现内存使用在上跳下窜,但是永远没有超过一个限额。程序也会输出大量的“A/B desctruct”,这说明析构函数被调用了。

我的同事的程序中,就存在这种引用的环路,而他的脚本,实在php5.2.3下执行的。simple_html_dom工具中,有两个类,分别是simple_html_dom和simple_html_dom_node,前者中有一个数组成员变量nodes,数组中每个元素都是一个simple_html_dom_node对象;而每个simple_html_dom_node对象都有一个成员变量dom,该dom的值就是前面的simple_html_dom对象——这样就形成了一个漂亮的引用环,导致了内存泄露。解决的办法也很简单,就是simple_html_dom对象在使用完毕时,主动调用其clear函数,清空其成员变量nodes,环就被打破了,内存泄露也就不会发生了。

其他

1. 垃圾回收的时机

PHP中,引用计数为0,则内存立刻释放。也就是说,不存在环状引用的变量,离开变量的作用域,内存被立刻释放。环状引用检测则是在满足一定条件下触发,所以在上面的例子中,会看到使用的内存有大幅度的波动。也可以通过 gc_collect_cycles 函数来主动进行环状引用检测。

2. &符号的影响

显式引用一个变量,会增加该内存的引用计数:

$a = "something";
$b = &$a;

此时unset($a), 但是仍有$b指向该内存区域的引用,内存不会释放。

3. unset函数的影响

unset只是断开一个变量到一块内存区域的连接,同时将该内存区域的引用计数-1;在上面的例子中,循环体内部,$a=new A(); unset($a);并不会将$a的引用计数减到零;

4. = null 操作的影响

$a = null 是直接将$a 指向的数据结构置空,同时将其引用计数归0。

5. 脚本执行结束的影响

脚本执行结束,该脚本中使用的所有内存都会被释放,不论是否有引用环。

原文地址:http://blog.snsgou.com/post-181.html

时间: 2024-08-27 20:53:09

PHP的内存泄露问题与垃圾回收的相关文章

(转)Java 内存区域分配和垃圾回收(GC)机制

Java垃圾回收概况 Java GC(Garbage Collection,垃圾收集,垃圾回收)机制,是Java与C++/C的主要区别之一,作为Java开发者,一般不需要专门编写内存回收和垃圾清理代 码,对内存泄露和溢出的问题,也不需要像C程序员那样战战兢兢.这是因为在Java虚拟机中,存在自动内存管理和垃圾清扫机制.概括地说,该机制对 JVM(Java Virtual Machine)中的内存进行标记,并确定哪些内存需要回收,根据一定的回收策略,自动的回收内存,永不停息(Nerver Stop

内存分析_.Net垃圾回收介绍

垃圾回收 1.       .Net垃圾回收中涉及的名称 1.1.什么是代? 垃圾回收器为了提升性能使用了代的机制,共分为三代(Gen0.Gen1.Gen2).GC工作机制基于以下假设, 1)  对象越新,生存期越短 2)  对象越老,生存期越长 3)  回收堆的一部分比回收整个堆时间短 在应用程序的生命周期中,最近新建的对象被分配在第0代,在一次垃圾回收之后存活下来的进入下一代.这样可以使GC专注于回收最有可能存在更多可回收对象的第0代(最近分配的最有可能很快被释放) 1.2 什么时候发生垃圾

JVM内存管理和JVM垃圾回收机制

JVM内存管理和JVM垃圾回收机制(1) 这里向大家描述一下JVM学习笔记之JVM内存管理和JVM垃圾回收的概念,JVM内存结构由堆.栈.本地方法栈.方法区等部分组成,另外JVM分别对新生代和旧生代采用不同的垃圾回收机制. AD: 你对JVM内存组成结构和JVM垃圾回收机制是否熟悉,这里和大家简单分享一下,希望对你的学习有所帮助,首先来看一下JVM内存结构,它是由堆.栈.本地方法栈.方法区等部分组成,结构图如下所示. JVM学习笔记 JVM内存管理和JVM垃圾回收 JVM内存组成结构 JVM内存

JVM虚拟机-03、JVM内存分配机制与垃圾回收算法

JVM虚拟机-03.JVM内存分配机制与垃圾回收算法 1 JVM内存分配与回收 1.1 对象优先在Eden区分配 大多数情况下,对象在新生代中?Eden?区分配.当?Eden?区没有足够空间进行分配时,虚拟机将发起一次Minor?GC.我们来进行实际测试一下.在测试之前我们先来看看?Minor?GC和Full?GC?有什么不同呢? Minor?GC/Young?GC:指发生新生代的的垃圾收集动作,MinorGC非常频繁,回收速度一般也比较快. Major?GC/Full?GC:一般会回收老年代,

JVM的内存区域划分以及垃圾回收机制详解

在我们写Java代码时,大部分情况下是不用关心你New的对象是否被释放掉,或者什么时候被释放掉.因为JVM中有垃圾自动回收机制.在之前的博客中我们聊过Objective-C中的MRC(手动引用计数)以及ARC(自动引用计数)的内存管理方式,下方会对其进行回顾.而目前的JVM的内存回收机制则不是使用的引用计数,而是主要使用的"复制式回收"和"自适应回收". 当然除了上面是这两种算法外,还有其他是算法,下方也将会对其进行介绍.本篇博客,我们先简单聊一下JVM的区域划分,

JVM内存管理机制和垃圾回收机制

JVM自身结构物理图: Java代码编译和执行的整个过程包含了以下三个重要的机制: 1.java源码编译机制 1)分析和输入到符号表 class文件结构包含: 结构信息.包括class文件格式版本号及各部分的数量与大小的信息 元数据.对应于Java源码中声明与常量的信息.包含类/继承的超类/实现的接口的声明信息.域与方法声明信息和常量池 方法信息.对应Java源码中语句和表达式对应的信息.包含字节码.异常处理器表.求值栈与局部变量区大小.求值栈的类型记录.调试符号信息 2.类加载机制 1)Boo

JVM堆内存控制/分代垃圾回收

JVM的堆的内存, 是通过下面面两个参数控制的 -Xms 最小堆的大小, 也就是当你的虚拟机启动后, 就会分配这么大的堆内存给你 -Xmx 是最大堆的大小 当最小堆占满后,会尝试进行GC,如果GC之后还不能得到足够的内存(GC未必会收集到所有当前可用内存),分配新的对象,那么就会扩展堆,如果-Xmx设置的太小,扩展堆就会失败,导致OutOfMemoryError错误提示. 实际上,细节不止于此, 堆还会被分成几个不同的区域,分别应用不同的GC算法 http://unixboy.javaeye.c

JVM内存组成结构以及垃圾回收

JVM内存组成结构 JVM栈由堆.栈.本地方法栈.方法区等部分组成,结构图如下所示: 1)堆 所有通过new创建的对象的内存都在堆中分配,其大小可以通过-Xmx和-Xms来控制.堆被划分为新生代和旧生代,新生代又被进一步划分为Eden和Survivor区,最后Survivor由From Space和To Space组成,结构图如下所示: 新生代.新建的对象都是用新生代分配内存,Eden空间不足的时候,会把存活的对象转移到Survivor中,新生代大小可以由-Xmn来控制,也可以用-XX:Surv

PHP内存管理机制与垃圾回收机制

PHP内存管理机制 1 var_dump(memory_get_usage()); //获取内存 2 $a = "laruence"; //定义一个变量 3 var_dump(memory_get_usage()); //定义变量之后获取内存 4 unset($a); //删除该变量 5 var_dump(memory_get_usage()); //删除变量后获取内存 6 从上面可以看出php的内存管理机制是:预先给出一块空间,用来存储变量,当空间不够时,再申请一块新的空间. 1.存