在OutOfMemory异常时让PHP转储堆栈信息

3

我目前正在调试一个脚本,它经常遇到OutOfMemory异常。它作为cronjob运行,并且通常运行良好,但是当cronjob由于某种原因一段时间没有运行时,脚本必须处理太多排队的元素,就会遇到OutOfMemory异常。

通过检查代码,我无法发现问题所在。我认为其中一个迭代函数调用可能会泄漏内存,但我不确定哪个函数和在哪里。 当OutOfMemory异常发生时,有没有选项可以让PHP转储堆?我可能能够从那里找到问题(最有可能)。


http://de3.php.net/manual/en/function.memory-get-peak-usage.php - teemitzitrone
我知道峰值使用量,那正好是脚本崩溃时我的内存限制。除此之外,我不知道这怎么能帮到我。 - Daniel Baulig
6个回答

5

虽然我没有找到“异常时转储堆”的选项,但我找到了get_defined_vars(),如果从全局范围调用,它基本上就是一个堆转储。使用它,我能够看到我的内存中仍有数百(实际上是数千)个仍在引用的数据库行挂起。这是由于某个问题函数中未释放的mysql结果资源导致的泄漏。我找到了并修复了它。现在运行良好。


你是如何使用get_defined_vars()来准确定位问题的?你只是运行脚本一段时间然后让它转储,还是有一种方法可以在脚本遇到内存泄漏时触发调用该函数? - Jordan Feldstein
1
我实现了一个迭代计数器,它可以在崩溃之前向我显示大约迭代的次数,对于我的情况,这个数字在20000到22000之间。然后我让脚本运行,并在第18000次迭代时使用get_defined_vars来转储堆。在我的情况下,很明显是什么导致了内存混乱。 - Daniel Baulig
为了使事情更加清晰,我将每个迭代的 get_defined_vars() 转储为 json 文件。然后通过 jq . 运行这些文件以获取类似格式的输出。一旦你做到了这一点,就可以比较这两个文件,并且很可能会发现增加了某些内容。根据这些信息,我回到了上一个转储文件并找到了新增的项目。 - Mat Schaffer

1

最简单的方法是,在脚本可能出错的部分使用try-catch块,并在catch部分中转储堆栈。问题可能是机器无法响应,因为内存已满并终止。我不知道丢弃一些变量是否有助于释放一些内存以输出一些数据。

编辑:为此,请使用php函数debug-backtrace。这将给您提供一个堆栈跟踪。因此,如果机器仍在运行,则很可能能够找到错误。


我已经知道脚本在哪个部分停止运行了。它并不总是在代码的确切行中,但总是在某个循环内。我怀疑该循环中存在内存泄漏,然而,该循环的堆栈非常深(因此有许多函数调用,函数调用中的函数调用),脚本崩溃的点不一定是脚本泄漏的地方。事实上,它总是在各种不同的点崩溃(可能是由于非确定性垃圾回收)。 - Daniel Baulig
由于嵌套的函数调用,这段代码很难理解——大约1000行,可能稍微多一点。 - Daniel Baulig
如果您知道错误发生在第435个循环之后,那会更容易调试。在这种情况下,您可以检查第435个循环并打印调试消息(然后,当待检查的代码行数为n时,您可以在O(log n)时间内找到错误)。请确保在检查循环次数时每次重新启动服务器,以清空服务器使用的内存。 - Thariama
我认为知道错误发生在第435次迭代上对我没有帮助,因为循环正在处理一堆表行并将它们更新回去。表行基本相同(当然有不同的值,但没有像blob这样的花哨或变长)。每次迭代基本相同,除了一个行得到值A,另一个行得到值B。当然,这是简化了的,但最终归结为这个。确定行是否获得值A或B当然是复杂的,并涉及大量的内容。 - Daniel Baulig
显示剩余6条评论

0

0

这是我能够快速用PHP编写的“堆转储”的最佳版本。我获取定义的变量和函数,然后按其序列化长度进行排序。序列化长度并不是获取变量大小的100%可靠方法,但它相当不错,并且通常有助于确定哪些对象是您的内存占用量大的对象:

$memmap = array_map(function($var) { return strlen(serialize($var)); }, array_merge(get_defined_functions(), get_defined_vars())); arsort($memmap); var_dump($memmap);

如果您希望结果更详细或递归遍历定义的变量,则可能需要稍微调整回调函数。


0

只需在处理对象时逐个读取,而不是将所有对象一起加载到内存中?


从目测代码来看,这似乎是脚本的作用。正如我所说,我怀疑存在内存泄漏。可能是由于循环引用(这是 PHP 5.0),但通过逐行阅读数千行代码来调试以便找到问题只是浪费时间。 - Daniel Baulig
你需要在 PHP 中有创意才能创建一个内存泄漏,除非你只是把数据放入数组中(可能使用一些奇怪的关联键),而不将其删除... - joni
你的意思是像创建递归关联这样有创意的东西吗?例如,它们会在5.3之前的版本中创建内存泄漏,因为垃圾回收无法检测到它们。由于我正在调试的代码不是我的,所以我不知道可能发生了什么问题。 - Daniel Baulig

0

我在使用simpleXML时遇到了很多问题和内存泄漏。这些问题非常难以跟踪......花费我好几天时间才弄清楚是simpleXML导致的并解决了它们。 据我所知,您可以编程设置OOM的句柄:)

此外,PHP用于显示内存信息的函数无法检测到内存泄漏,我有一些脚本占用了约1GB的内存,但PHP的函数仅报告使用了100MB:)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接