PHP在循环中使用反射类时占用过多内存的解决方法
我有一个这样的环PHP在循环中使用反射类时占用过多内存的解决方法,php,memory,memory-management,reflection,memory-leaks,Php,Memory,Memory Management,Reflection,Memory Leaks,我有一个这样的环 foreach ($classes as $class) { $reflectionClass = new \ReflectionClass($class); ... /*code that doesn't matter - commenting it out leaves the memory consumption all the same */ } 这导致了致命错误:允许内存大小为134
foreach ($classes as $class)
{
$reflectionClass = new \ReflectionClass($class);
... /*code that doesn't matter - commenting it out leaves the
memory consumption all the same */
}
这导致了致命错误:允许内存大小为134217728字节,如果要循环的类太多,php.ini中允许的内存大小相对较小(对于我来说,大约是4000个类和128 MB内存设置),则会导致内存耗尽
循环开始前的内存使用量约为1.6MB
正如您可能猜到的,将unset($reflectionClass)
放在循环体的末尾一点帮助都没有
通过谷歌搜索,我猜PHP不会释放对象占用的内存,以防它有一些对其他对象的内部引用。
现在,这些帖子(,)让我尝试使用垃圾收集器:
gc_enable();
foreach ($classes as $class)
{
$reflectionClass = new \ReflectionClass($class);
...
unset($reflectionClass);
gc_collect_cycles();
}
这仍然会导致相同的结果
我看到的解决方案是:
1) 增加允许的内存设置-这是丑陋和悲伤的。
2) 将类分成多个部分,并通过分叉或执行其他一些PHP脚本分别从每个部分获得所需的结果——但这听起来很难做到
内存泄漏是否有简单的解决方法?我是不是遗漏了什么
更新
多亏了Paul Crovella指出,保留加载的类定义会占用内存,事实上这不是泄漏
因此,它可以通过在子进程中执行或通过另一个脚本来解决。添加了我自己的解决方案作为答案。通过将部分类的处理委托给子进程实现了一种变通方法,子进程在退出时将所有加载的类定义带到Hades 看起来像这样:
foreach (array_chunk($classes, 300) as $classesPortion)
{
if (socket_create_pair(AF_UNIX, SOCK_STREAM, 0, $socketArray) === false)
throw new \Exception('Could not create socket');
$pid = pcntl_fork();
if ($pid === -1) //Forking failed
{
throw new \Exception('Could not fork process');
}
elseif ($pid === 0) //Is child process
{
socket_close($socketArray[1]);
foreach ($classesPortion as $class)
{
$data = ...; //generating needed data
}
$dataString = serialize($data);
if (!socket_write($socketArray[0], $dataString, strlen($dataString)))
{
throw new \Exception('Failed to write to socket');
}
socket_close($socketArray[0]);
exit(0);
}
else //Is parent process
{
socket_close($socketArray[0]);
pcntl_waitpid($pid, $childProcessStatus);
if ($childProcessStatus !== 0)
{
throw new \Exception('Child process exited abnormally');
}
else
{
$result = socket_read($socketArray[1], 1000000, PHP_BINARY_READ);
... // deal with result
}
socket_close($socketArray[1]);
}
}
通过将类的一部分的处理委托给子进程实现了一种变通方法,子进程在退出时将所有加载的类定义带到Hades 看起来像这样:
foreach (array_chunk($classes, 300) as $classesPortion)
{
if (socket_create_pair(AF_UNIX, SOCK_STREAM, 0, $socketArray) === false)
throw new \Exception('Could not create socket');
$pid = pcntl_fork();
if ($pid === -1) //Forking failed
{
throw new \Exception('Could not fork process');
}
elseif ($pid === 0) //Is child process
{
socket_close($socketArray[1]);
foreach ($classesPortion as $class)
{
$data = ...; //generating needed data
}
$dataString = serialize($data);
if (!socket_write($socketArray[0], $dataString, strlen($dataString)))
{
throw new \Exception('Failed to write to socket');
}
socket_close($socketArray[0]);
exit(0);
}
else //Is parent process
{
socket_close($socketArray[0]);
pcntl_waitpid($pid, $childProcessStatus);
if ($childProcessStatus !== 0)
{
throw new \Exception('Child process exited abnormally');
}
else
{
$result = socket_read($socketArray[1], 1000000, PHP_BINARY_READ);
... // deal with result
}
socket_close($socketArray[1]);
}
}
我无法重现这个问题。您使用的是什么版本的PHP?版本是5.4.15。奇怪的是,它对您来说是不可复制的,我一直在为每个循环增加+30-130KB,这取决于类内容。等等。您确定这是由于反射而导致的泄漏,而不仅仅是加载这些类定义的正常成本吗?它们在你循环之前加载了吗?这是一个很好的观点,谢谢!在循环的开头,只存在一个包含类名的字符串数组。按名称创建一个
ReflectionClass
肯定会加载类定义,但后者(或不应该)是否会被卸载,特别是在取消设置使用它的对象之后?不,类定义不会被卸载,也不应该被卸载。如果每次有人想要另一个对象时,自动加载器都必须返回磁盘读取,这将变成一场i/o噩梦。类定义是应用程序自然权重的一部分,它们不是漏洞。我无法重新创建此问题。您使用的是什么版本的PHP?版本是5.4.15。奇怪的是,它对您来说是不可复制的,我一直在为每个循环增加+30-130KB,这取决于类内容。等等。您确定这是由于反射而导致的泄漏,而不仅仅是加载这些类定义的正常成本吗?它们在你循环之前加载了吗?这是一个很好的观点,谢谢!在循环的开头,只存在一个包含类名的字符串数组。按名称创建一个ReflectionClass
肯定会加载类定义,但后者(或不应该)是否会被卸载,特别是在取消设置使用它的对象之后?不,类定义不会被卸载,也不应该被卸载。如果每次有人想要另一个对象时,自动加载器都必须返回磁盘读取,这将变成一场i/o噩梦。类定义是应用程序自然权重的一部分,它们不是漏洞。