如何避免 PHP 5.4 中的内存泄漏?
How to avoid memory leaks in PHP 5.4?
我刚刚发现在 PHP 中可能会泄漏内存。我在循环中 运行 一些代码,每次循环后内存使用量都会增加,直到脚本达到内存限制。我已经确定:
- 没有全局变量(我相信也没有静态变量)
- 我是 运行 PHP 5.4,据说它有这个用于循环引用的新垃圾收集器
- 每次循环后我的所有变量都超出范围
- 我在每个周期后调用
gc_collect_cycles()
这是一个示例脚本,演示了与 PhpExcel library 有关的问题:
require_once(__DIR__ . '/libraries/PHPExcel/PHPExcel.php');
ini_set('memory_limit', '200M');
@mkdir(__DIR__ . '/output');
gc_enable();
for ($n = 0 ; $n < 10 ; $n++)
{
do_it($n);
gc_collect_cycles();
}
function do_it($n)
{
echo 'Round '.$n.'...';
$text = str_repeat('x', 50000);
$phpexcel = new PHPExcel();
$worksheet = $phpexcel->getActiveSheet();
for ($r = 1 ; $r < 50 ; $r++)
for ($c = ord('A') ; $c <= ord('S') ; $c++)
$worksheet->setCellValueExplicit(chr($c) . $r, $text, PHPExcel_Cell_DataType::TYPE_STRING);
// $phpexcel->disconnectWorksheets();
unset($phpexcel, $worksheet);
echo 'done, now using ' . round((memory_get_usage()) / 1024 / 1024).' MB' . "\n";
}
输出:
Round 0...done, now using 41 MB
Round 1...done, now using 80 MB
Round 2...done, now using 123 MB
Round 3...done, now using 157 MB
Round 4...
Fatal error: Allowed memory size of 209715200 bytes exhausted (tried to allocate 36 bytes)
现在针对这个特定问题 the solution is 在每个循环后调用 $phpexcel->disconnectWorksheets();
,这会取消设置一些对象成员。
真正的问题是:作为一名 PHP 程序员,我应该怎么做才能避免此类内存泄漏?在取消设置对象之前,我真的必须递归遍历每个对象以取消设置其成员吗?
disconnectWorksheets()
方法是在 PHP 5.3 新奇的垃圾收集之前添加的。
问题是 PHPExcel 有循环引用。 PHPExcel 对象引用其工作表对象,而各个工作表引用其父 PHPExcel 对象。同样,工作表对象引用其所有单元格(通过缓存的单元格集合),并且所有单元格都引用其父工作表。
这种类型的循环关系无法用旧的 PHP 垃圾收集器清除,它完全依赖于引用计数;这意味着如果在别处存在对对象的任何引用,则无法取消设置对象。
disconnectWorksheets()
提供了一种从下到上打破这些循环关系的简单方法:断开单元格与其父工作表的连接,以便仅存在非循环工作表 -> 单元格关系,并且 PHPExcel 对象及其工作表。
一旦循环关系被打破,一个简单的 unset()
应该可以工作。
但是,我从代码中看到您正在创建对工作表的单独引用:
$worksheet = $phpexcel->getActiveSheet();
所以这个引用不会被调用 disconnectWorksheets()
清除,对 PHPExcel 对象的引用也将保留在编写器中。
我怀疑它可能归结为
unset($phpexcel, $worksheet, $writer);
取消设置对象。
如果它首先尝试取消设置 $phpexcel
,那么它可能不能,因为在 $worksheet 中仍然有对它的引用,在 $writer 中还有另一个引用....也许颠倒了实体的顺序你取消设置会有所不同
unset($writer, $worksheet, $phpexcel);
或者可能在调用 disconnectWorksheets()
之前取消设置 $writer
和 $worksheet
;之后才取消设置 $phpexcel。
理论上,PHP5.3 的新垃圾回收应该可以处理这些循环引用,但在实践中我不确定它到底有多有效...我根本没有测试过.从理论上讲,它应该消除所有需要使用 disconnectWorksheets()
方法。
对于还在使用PHP <= 5.3的用户来说,disconnectWorksheets()
方法真的是保留了(PHP5.2.0仍然是最早支持的版本;信不信由你不,我仍然有人要求我在这个周末解决 5.1.16 下的问题 运行 PHPExcel)。然而,很可能还有其他循环引用,也许在样式关系中,没有被调用 disconnectWorksheets()
清除,所以我什至不能保证这一点很遗憾;但这是我能提供的最好建议。
这里的问题是静态数组 PHPExcel_Calculation::$_workbookSets
为每个工作簿获取对 PHPExcel_Calculation
对象的引用。每次 do_it()
运行时它都会增长。因此,由于对象永远不会真正超出范围,因此无法回收它们的内存及其属性等。
用 PHPExcel_Calculation::unsetInstance($phpexcel);
替换你的 unset(...);
并且内存泄漏消失了,因为这从该数组中删除了关联的对象(并且只 那个。 )
对于一般性问题:循环引用不是问题,垃圾收集器可以很好地处理它们 - 避免使用全局变量(静态变量只是花哨的全局变量),因为它们可以很好地隐藏并且膨胀失控。
我刚刚发现在 PHP 中可能会泄漏内存。我在循环中 运行 一些代码,每次循环后内存使用量都会增加,直到脚本达到内存限制。我已经确定:
- 没有全局变量(我相信也没有静态变量)
- 我是 运行 PHP 5.4,据说它有这个用于循环引用的新垃圾收集器
- 每次循环后我的所有变量都超出范围
- 我在每个周期后调用
gc_collect_cycles()
这是一个示例脚本,演示了与 PhpExcel library 有关的问题:
require_once(__DIR__ . '/libraries/PHPExcel/PHPExcel.php');
ini_set('memory_limit', '200M');
@mkdir(__DIR__ . '/output');
gc_enable();
for ($n = 0 ; $n < 10 ; $n++)
{
do_it($n);
gc_collect_cycles();
}
function do_it($n)
{
echo 'Round '.$n.'...';
$text = str_repeat('x', 50000);
$phpexcel = new PHPExcel();
$worksheet = $phpexcel->getActiveSheet();
for ($r = 1 ; $r < 50 ; $r++)
for ($c = ord('A') ; $c <= ord('S') ; $c++)
$worksheet->setCellValueExplicit(chr($c) . $r, $text, PHPExcel_Cell_DataType::TYPE_STRING);
// $phpexcel->disconnectWorksheets();
unset($phpexcel, $worksheet);
echo 'done, now using ' . round((memory_get_usage()) / 1024 / 1024).' MB' . "\n";
}
输出:
Round 0...done, now using 41 MB
Round 1...done, now using 80 MB
Round 2...done, now using 123 MB
Round 3...done, now using 157 MB
Round 4...
Fatal error: Allowed memory size of 209715200 bytes exhausted (tried to allocate 36 bytes)
现在针对这个特定问题 the solution is 在每个循环后调用 $phpexcel->disconnectWorksheets();
,这会取消设置一些对象成员。
真正的问题是:作为一名 PHP 程序员,我应该怎么做才能避免此类内存泄漏?在取消设置对象之前,我真的必须递归遍历每个对象以取消设置其成员吗?
disconnectWorksheets()
方法是在 PHP 5.3 新奇的垃圾收集之前添加的。
问题是 PHPExcel 有循环引用。 PHPExcel 对象引用其工作表对象,而各个工作表引用其父 PHPExcel 对象。同样,工作表对象引用其所有单元格(通过缓存的单元格集合),并且所有单元格都引用其父工作表。
这种类型的循环关系无法用旧的 PHP 垃圾收集器清除,它完全依赖于引用计数;这意味着如果在别处存在对对象的任何引用,则无法取消设置对象。
disconnectWorksheets()
提供了一种从下到上打破这些循环关系的简单方法:断开单元格与其父工作表的连接,以便仅存在非循环工作表 -> 单元格关系,并且 PHPExcel 对象及其工作表。
一旦循环关系被打破,一个简单的 unset()
应该可以工作。
但是,我从代码中看到您正在创建对工作表的单独引用:
$worksheet = $phpexcel->getActiveSheet();
所以这个引用不会被调用 disconnectWorksheets()
清除,对 PHPExcel 对象的引用也将保留在编写器中。
我怀疑它可能归结为
unset($phpexcel, $worksheet, $writer);
取消设置对象。
如果它首先尝试取消设置 $phpexcel
,那么它可能不能,因为在 $worksheet 中仍然有对它的引用,在 $writer 中还有另一个引用....也许颠倒了实体的顺序你取消设置会有所不同
unset($writer, $worksheet, $phpexcel);
或者可能在调用 disconnectWorksheets()
之前取消设置 $writer
和 $worksheet
;之后才取消设置 $phpexcel。
理论上,PHP5.3 的新垃圾回收应该可以处理这些循环引用,但在实践中我不确定它到底有多有效...我根本没有测试过.从理论上讲,它应该消除所有需要使用 disconnectWorksheets()
方法。
对于还在使用PHP <= 5.3的用户来说,disconnectWorksheets()
方法真的是保留了(PHP5.2.0仍然是最早支持的版本;信不信由你不,我仍然有人要求我在这个周末解决 5.1.16 下的问题 运行 PHPExcel)。然而,很可能还有其他循环引用,也许在样式关系中,没有被调用 disconnectWorksheets()
清除,所以我什至不能保证这一点很遗憾;但这是我能提供的最好建议。
这里的问题是静态数组 PHPExcel_Calculation::$_workbookSets
为每个工作簿获取对 PHPExcel_Calculation
对象的引用。每次 do_it()
运行时它都会增长。因此,由于对象永远不会真正超出范围,因此无法回收它们的内存及其属性等。
用 PHPExcel_Calculation::unsetInstance($phpexcel);
替换你的 unset(...);
并且内存泄漏消失了,因为这从该数组中删除了关联的对象(并且只 那个。 )
对于一般性问题:循环引用不是问题,垃圾收集器可以很好地处理它们 - 避免使用全局变量(静态变量只是花哨的全局变量),因为它们可以很好地隐藏并且膨胀失控。