什么是“zend_mm_heap corrupted”的意思?

133

最近我的应用程序出现了之前从未遇到过的问题。我决定查看Apache的错误日志,结果发现一个错误消息,显示"zend_mm_heap corrupted"。这是什么意思。

操作系统:Fedora Core 8 Apache版本:2.2.9 PHP版本:5.2.6


3
我使用了USE_ZEND_ALLOC=0来获取错误日志中的堆栈跟踪信息,发现了一个错误/usr/sbin/httpd: corrupted double-linked list,我发现注释掉opcache.fast_shutdown=1对我有用。 - Spidfire
是的,我也是。还可以看到下面的另一个报告 https://dev59.com/AHE95IYBdhLWcg3wlu4g#35212026 - lkraav
我在使用Laravel时遇到了同样的问题。我将一个类注入到另一个类的构造函数中。我注入的类又注入了它所注入的类,从而导致了堆问题。简而言之,就是创建了一个循环引用。 - Thomas
1
重新启动Apache服务器是最快和临时的解决方案 :) - Leopathu
41个回答

61

这不是必然可以通过更改配置选项来解决的问题。

更改配置选项有时会产生积极影响,但同样容易使事情变得更糟,或者根本没有任何作用。

错误的性质如下:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int main(void) {
    void **mem = malloc(sizeof(char)*3);
    void *ptr;

    /* read past end */
    ptr = (char*) mem[5];   

    /* write past end */
    memcpy(mem[5], "whatever", sizeof("whatever"));

    /* free invalid pointer */
    free((void*) mem[3]);

    return 0;
}

上述代码可以使用以下方式进行编译:

gcc -g -o corrupt corrupt.c

使用Valgrind执行代码,您可以看到许多内存错误,最终导致分段错误:

krakjoe@fiji:/usr/src/php-src$ valgrind ./corrupt
==9749== Memcheck, a memory error detector
==9749== Copyright (C) 2002-2013, and GNU GPL'd, by Julian Seward et al.
==9749== Using Valgrind-3.10.1 and LibVEX; rerun with -h for copyright info
==9749== Command: ./corrupt
==9749== 
==9749== Invalid read of size 8
==9749==    at 0x4005F7: main (an.c:10)
==9749==  Address 0x51fc068 is 24 bytes after a block of size 16 in arena "client"
==9749== 
==9749== Invalid read of size 8
==9749==    at 0x400607: main (an.c:13)
==9749==  Address 0x51fc068 is 24 bytes after a block of size 16 in arena "client"
==9749== 
==9749== Invalid write of size 2
==9749==    at 0x4C2F7E3: memcpy@@GLIBC_2.14 (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==9749==    by 0x40061B: main (an.c:13)
==9749==  Address 0x50 is not stack'd, malloc'd or (recently) free'd
==9749== 
==9749== 
==9749== Process terminating with default action of signal 11 (SIGSEGV): dumping core
==9749==  Access not within mapped region at address 0x50
==9749==    at 0x4C2F7E3: memcpy@@GLIBC_2.14 (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==9749==    by 0x40061B: main (an.c:13)
==9749==  If you believe this happened as a result of a stack
==9749==  overflow in your program's main thread (unlikely but
==9749==  possible), you can try to increase the size of the
==9749==  main thread stack using the --main-stacksize= flag.
==9749==  The main thread stack size used in this run was 8388608.
==9749== 
==9749== HEAP SUMMARY:
==9749==     in use at exit: 3 bytes in 1 blocks
==9749==   total heap usage: 1 allocs, 0 frees, 3 bytes allocated
==9749== 
==9749== LEAK SUMMARY:
==9749==    definitely lost: 0 bytes in 0 blocks
==9749==    indirectly lost: 0 bytes in 0 blocks
==9749==      possibly lost: 0 bytes in 0 blocks
==9749==    still reachable: 3 bytes in 1 blocks
==9749==         suppressed: 0 bytes in 0 blocks
==9749== Rerun with --leak-check=full to see details of leaked memory
==9749== 
==9749== For counts of detected and suppressed errors, rerun with: -v
==9749== ERROR SUMMARY: 4 errors from 3 contexts (suppressed: 0 from 0)
Segmentation fault
如果你不知道,现在你已经明白了 mem 是堆分配内存;堆指程序在运行时显式请求的内存区域(例如我们使用的 malloc 函数)。
如果你尝试运行这段糟糕的代码,你会发现并非所有那些明显错误的语句都会导致段错误(一种致命的终止错误)。
我故意在示例代码中制造了这些错误,但是在内存管理环境中,同样类型的错误非常容易发生:如果某些代码没有正确地维护变量(或其他符号)的引用计数,例如如果它太早释放该变量,那么另一段代码可能会从已释放的内存中读取,如果某些代码存储地址不正确,则另一段代码则可能会写入无效的内存,它可能被多次释放...
这些都不是在 PHP 中可以调试的问题,它们绝对需要内部开发人员的关注。
应该采取以下步骤:
  1. http://bugs.php.net上开启一个错误报告
    • 如果你有段错误,请尝试提供回溯信息
    • 包括尽可能多的配置信息,特别是如果您使用的是 opcache,则包括优化级别。
    • 保持检查错误报告以获取更新,可能会请求更多信息。
  2. 如果您已加载了 opcache,请禁用优化
    • 我不想针对 opcache,它很棒,但是其中一些优化已知会导致故障。
    • 如果这样做没有效果,尽管您的代码可能会变慢,请先卸载 opcache。
    • 如果任何一个操作改变或修复了问题,请更新您所做的错误报告。
  3. 立即禁用所有不必要的扩展程序
    • 逐个启用所有扩展,并在每次配置更改后彻底进行测试。
    • 如果找到有问题的扩展程序,请使用更多信息更新您的错误报告。
  4. 赚取利润。
可能不会有任何利润...我一开始就说过,你可能会通过调整配置来改变症状,但这非常靠运气,而且不能帮助下一次出现相同的 zend_mm_heap corrupted 错误,因为只有那么多的配置选项。
当我们发现错误时,创建 bug 报告非常重要,我们不能假定下一个遇到该错误的人会这样做... 很可能,实际解决方法并不神秘,只要让相关人员知道问题即可。

USE_ZEND_ALLOC

如果在环境中设置 USE_ZEND_ALLOC=0,则会禁用 Zend 自己的内存管理器;Zend 的内存管理器确保每个请求都有自己的堆,所有内存都在请求结束时被释放,并且针对 PHP 正好大小的内存块进行了优化的分配。
禁用它将禁用这些优化,更重要的是可能会导致内存泄漏,因为有很多扩展程序代码依赖于 Zend MM 以在请求结束时为它们释放内存(指摇头)。
它还可能隐藏症状,但是系统堆可以以与 Zend 堆

那么潜在的问题可能是你正在运行哪个版本的PHP? - Ishmael
@Ishmael 是的,还要检查所有扩展的版本,因为警告可能是由某个扩展引起的。 - bishop
4
对我来说,这个答案似乎是最好的。我个人已经几次遇到过这个问题,而且它总是与有问题的扩展(在我的情况下是Enchant拼写库)有关。除了php本身,还可能是错误的环境(库版本不匹配、错误的依赖关系等)。 - Fractalizer
2
到目前为止,这个问题的最佳答案,以及许多类似问题的最佳答案。 - Nikita 웃
这个回答确实很有指导意义,但我认为应用程序开发者的工作并不是调试服务器核心。如果你有完整的堆栈跟踪,那肯定更容易,但接下来呢?要求在拉取请求中修复它吗?并非每个人都是 DevOps 或能够理解像 C 这样的低级语言。相反,也存在这种情况。所以最终我认为,如果开发者一开始就不犯内存管理错误,那会更容易。正如你所建议的那样,这在使用 opcache 时很常见,但并不令人惊讶,因为你知道一些开发人员懂得如何进行开发。 - job3dot5
1
我并没有建议开发人员调试问题。我用易于理解的代码和语言解释了问题,并建议他们创建一个错误报告,最后给了他们有关创建和维护有用的错误报告的建议。在这里唯一需要做的就是创建一个错误报告,调整设置、扩展、版本和环境变量只会带来可怕的猜测;有人可以在两秒钟内解决问题,你不需要调试它,也不需要成为C大师,甚至不需要知道GDB如何工作,只需向正确的人发送邮件(报告),问题就会消失。 - Joe Watkins

59

经过多次尝试,我发现如果在php.ini文件中增加output_buffering的值,这个错误就会消失。


66
增加到什么程度?为什么这个更改会让错误消失? - JDS
2
@JDS 这个答案有助于解释 output_buffering 是什么,以及为什么增加它可以帮助 https://dev59.com/CXE85IYBdhLWcg3wVR6g#2832179 - andrewtweber
8
@andrewtweber 我知道"ob"是什么意思,但我想知道dsmithers没有提到的具体细节,因为我遇到了与op相同的错误信息。最终我发现我的问题是与memcached相关的设置配置不正确。感谢你的回复! - JDS
3
我们的服务平台在生产中使用Memcache。然而,一些单个实例——非生产/沙盒、客户自定义——不使用Memcache。在后一种情况下,我将一个从生产环境复制的配置应用到了一个客户自定义的实例上,并且该Memcache配置指定了在该环境中无法使用的Memcache服务器URI。我删除了这一行并在应用程序中禁用了Memcache,问题就解决了。所以,长话短说,这是一个非常具体的问题,只出现在特定的环境中,可能不适用于普遍情况。但既然你问了…… - JDS
对我来说,解决方案是@Justin MacLeod的答案。我已经启用了输出缓冲区,并增加其大小并没有帮助。 - ioleo
显示剩余5条评论

51
我在使用PHP 5.5时也遇到了相同的错误,增加输出缓冲并没有帮助。我也没有运行APC,所以那不是问题所在。最后我找到了问题出现在opcache,我只需要从cli中禁用它即可。这有一个特定的设置:
opcache.enable_cli=0

一旦切换了zend_mm_heap,"zend_mm_heap corrupted" 错误就消失了。


这里有相同的问题和解决方案!谢谢! - Mauricio Sánchez
2
这篇文章真是太棒了,加一分!我们尝试了各种方法,但最终只有这个有效。 - Geoffrey Brier
7
我确定你知道cli是php的命令行版本,与例如与apache web服务器一起使用的php模块无关。我很好奇在cli上禁用opcache是如何有所帮助的?(我假设这是在Web服务器上发生的) - BIOHAZARD
这应该是回答这个问题的正确答案。提高output_buffering并不是解决方案,因为根据php.ini中的文档,这可能会对您的网站或应用程序产生负面影响。 - BlueCola
在所有回答中,这个选项最终让我在我的Docker设置中消除了错误,用于Drupal开发(Docker for Mac 17.06,Apache 2.4,PHP 7.1,Drupal 8.4)。 - Paul
显示剩余2条评论

46

如果你在Linux系统上,尝试在命令行中运行以下命令

export USE_ZEND_ALLOC=0

这个救了我!我将它添加到php-fpm服务文件中(systemd覆盖)。 - fzerorubigd
这对我有用。如果您在运行具有从ppas(apt)安装的apache和php的ubuntu服务器上,则请记得将此行添加到“/etc/apache2/envvars”中。当我从ondrej的存储库安装PHP 7.0-RC4时,开始出现此错误。 - Pedro Cordeiro
还有它可以在Windows上运行:set USE_ZEND_ALLOC=0 - Nabi K.A.Z.

23

检查 unset()。确保在析构函数中不要 unset()$this(或等价物)的引用并且在析构函数中,unset()不会导致同一对象的引用计数降至 0。我做了一些研究,发现这通常会导致堆损坏。

有一个关于 zend_mm_heap 损坏错误的PHP bug 报告。请参见评论 [2011-08-31 07:49 UTC] f dot ardelian at gmail dot com 中有关如何重现该错误的示例。

我有一种感觉,所有其他“解决方案”(更改 php.ini、使用较少模块的源代码编译 PHP 等)只是掩盖了问题。


6
我用简单的HTML DOM库遇到了问题,将代码中的unset改为$simplehtmldom->clear()后问题得以解决。谢谢! - alexkb

13

对我来说,之前的答案都没用,直到我尝试了以下方法:

opcache.fast_shutdown=0

到目前为止,那似乎是有效的。

我正在使用PHP 5.6和PHP-FPM以及Apache proxy_fcgi,如果这很重要的话...


1
有很多“我也是”的回复适用于各种不同的情况,但这似乎最类似于我的配置,然后 - 这个确切的更改似乎已经消除了我的问题。 - lkraav

6

在我的情况下,这个错误的原因是其中一个数组变得非常大。我已经设置了我的脚本,在每次迭代时重置数组,这样问题就解决了。


这对我有用 - 谢谢!我没想到垃圾回收器会释放循环引用的内存,所以我没有检查它。 - half-fast

5
根据故障追踪器,设置opcache.fast_shutdown=0。快速关闭使用Zend内存管理器来清理其混乱,这会禁用它。

这个问题困扰我们很久,但是在我们的CentOS Linux 7.2.1511版本和PHP 5.5.38上,我们终于解决了“zend_mm_heap corrupted”的问题。现在我们可以恢复使用opcode缓存,这对我们来说是昼夜的区别。 - Richard Ginsberg
谢谢提醒,这正是我的问题! - Weasler

4
我认为这里没有一个确定的答案,所以我会分享我的经验。我曾在一个cPanel服务器上遇到过这个错误,伴随着随机的httpd段错误。问题的症状是Apache会随机重置连接(在Chrome中未接收到数据,在Firefox中连接被重置)。这些看起来是随机的-大多数时间都可以正常工作,有时则不能。
当我到达现场时,输出缓冲区关闭了。通过阅读本帖子,它暗示了输出缓冲区,我打开了它(= 4096)以查看会发生什么。此时,它们全部开始显示错误。这很好,因为现在错误是可重复的。
我逐个禁用扩展程序。其中,eaccellerator、pdo、ioncube loader等,还有很多看起来可疑的扩展程序,但都没有起到帮助作用。
最后,我找到了淘气的PHP扩展程序“homeloader.so”,它似乎是一种cPanel-easy-installer模块。删除后,我没有遇到任何其他问题。
在这方面,它似乎是一个通用的错误消息,因此您的结果可能会因这些答案而异,最好的做法是:
使错误可重复(什么条件?)每次
找到共同点
有选择性地禁用任何PHP模块、选项等(或者,如果您赶时间,请将它们全部禁用以查看是否有帮助,然后有选择性地重新启用它们,直到再次出现问题)
如果这些都没有帮助,许多答案都暗示它可能与代码相关。同样,关键是使错误可重复每个请求,以便您可以缩小范围。如果您怀疑某段代码正在这样做,再次使错误可重复,只需删除代码,直到错误停止。一旦停止,您就知道最后删除的代码片段是罪魁祸首。
如果所有上述操作都失败了,您还可以尝试以下方法:
升级或重新编译PHP。希望修复导致您问题的任何错误。
将您的代码移动到另一个(测试)环境中。如果这解决了问题,那么发生了什么变化?php.ini选项?PHP版本?等等...
祝你好运。

3

我为这个问题苦苦挣扎了一个星期,这个解决方案对我有效,或者至少看起来是这样。

php.ini文件中进行以下更改。

report_memleaks = Off  
report_zend_debug = 0  

我的设置是:
Linux ubuntu 2.6.32-30-generic-pae #59-Ubuntu SMP  
with PHP Version 5.3.2-1ubuntu4.7  

这个方法没有起作用。

于是我尝试使用基准测试脚本,并尝试记录脚本停顿的位置。 我发现在错误之前,一个php对象被实例化,并且完成该对象应执行的操作需要超过3秒的时间,而在之前的循环中最多只需要0.4秒。我已经运行了这个测试很多次,每次都一样。我想,不妨尝试每次重复使用对象,而不是每次都创建新对象(这里有一个长循环)。到目前为止,我已经测试了这个脚本十几次,内存错误已经消失了!


1
这个方法之前有效,但现在错误又出现了。我该怎么停止它? - sam
这个方法在我的Mac Mavericks上使用MAMP PRO 2.1.1也有效。 - MutantMahesh
这个解决方案没有永久地解决问题,我又开始遇到这个错误了。 - MutantMahesh
7
这只是关闭错误报告而非解决问题,对吗? - Robert Went

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