欢迎光临
我们一直在努力

php方案 内存泄漏检测

内存泄漏检测


PHP 自带泄漏检测(最简单):

# 编译时开启
./configure enabledebug

# 运行时自动报告泄漏
php r "
\\$arr = [];
for (\\$i = 0; \\$i < 100; \\$i++) {
\\$arr[] = str_repeat('x', 1024);
}
// 故意不释放
"

# 进程退出时输出
# [Mon Jan 1 00:00:00 2024] Script: '-'
# /path/to/zend_alloc.c(123) : 4096 bytes leaked


Valgrind(最全面):

# 编译 PHP 时关掉内存管理器,让 Valgrind 接管
./configure enabledebug enablevalgrindchecks

# 运行
valgrind \\
leakcheck=full \\
showleakkinds=all \\
trackorigins=yes \\
suppressions=valgrind.supp \\ # 忽略PHP自身的"假泄漏"
php your_script.php 2>&1 | tee leak.log

# 输出
# ==1234== 1,024 bytes in 1 blocks are definitely lost
# ==1234== at 0x4C2FB0F: malloc
# ==1234== by 0x10A3B2: myext_create (myext.c:45) ← 你的代码
# ==1234== by 0x5F3A21: PHP_FUNCTION

# PHP 自带 Valgrind 抑制文件
valgrind suppressions=$(phpconfig prefix)/share/php/valgrind.supp \\
php test.php


AddressSanitizer(比 Valgrind 快):

# 编译时加上
CFLAGS="-fsanitize=address -g" \\
LDFLAGS="-fsanitize=address" \\
./configure enabledebug

# 直接跑,有泄漏自动报
php your_script.php

# 输出
# =================================================================
# ==1234==ERROR: LeakSanitizer: detected memory leaks
#
# Direct leak of 1024 byte(s) in 1 object(s) allocated from:
# #0 malloc (in libasan.so)
# #1 myext_alloc myext.c:45 ← 定位到行
# #2 zif_myext_open myext.c:120


扩展里常见泄漏场景:

// ❌ 场景1:分配了忘释放
PHP_FUNCTION(myext_get)
{
char *buf = emalloc(1024);
if (something_failed) {
RETURN_FALSE; // 直接返回,buf 泄漏
}
efree(buf);
RETURN_TRUE;
}

// ✅ 统一出口
PHP_FUNCTION(myext_get)
{
char *buf = emalloc(1024);
bool ok = false;

if (!something_failed) {
ok = true;
}

efree(buf); // 无论如何都释放
RETURN_BOOL(ok);
}

// ❌ 场景2:zend_string 引用计数没减
PHP_FUNCTION(myext_process)
{
zend_string *s = zend_string_init("hello", 5, 0);
// 用完忘了 release
RETURN_NULL();

// ✅
zend_string_release(s); // 减引用,为0才真正释放
RETURN_NULL();
}

// ❌ 场景3:HashTable 销毁了但内部数据没清
HashTable *ht = emalloc(sizeof(HashTable));
zend_hash_init(ht, 8, NULL, NULL, 0); // 析构函数传了NULL
zend_hash_add_ptr(ht, key, emalloc(64)); // 加进去的数据没人管

zend_hash_destroy(ht); // destroy 但析构函数是NULL,内部数据泄漏
efree(ht);

// ✅ 传正确的析构函数
zend_hash_init(ht, 8, NULL, efree, 0); // 或 ZVAL_PTR_DTOR

// ❌ 场景4:pemalloc 没有对应 pefree
PHP_MINIT_FUNCTION(myext) {
MY_G(cache) = pemalloc(sizeof(mycache), 1);
return SUCCESS;
}
// 忘了写 MSHUTDOWN

// ✅
PHP_MSHUTDOWN_FUNCTION(myext) {
pefree(MY_G(cache), 1);
MY_G(cache) = NULL;
return SUCCESS;
}


运行时监控内存增长:

// 每次请求后检查内存有没有持续增长
$before = memory_get_usage();

// 跑业务逻辑
handle_request();

$after = memory_get_usage();
$leak = $after $before;

if ($leak > 1024 * 1024) { // 增长超过 1MB 告警
error_log("Possible leak: {$leak} bytes after request");
}

// 长期监控,写到时序数据库
$peak = memory_get_peak_usage(true);
statsd_gauge('php.memory.peak', $peak);
// 画折线图,持续上涨 = 有泄漏


扩展开发调试宏:

// zend_alloc.h 里的调试宏
#ifdef ZEND_DEBUG
// 分配时记录文件和行号
_emalloc(size, __FILE__, __LINE__, __FUNCTION__, 0)

// 检查某个指针是否有效
ZEND_ASSERT(ptr != NULL);
#endif

// 手动 dump 当前所有分配
zend_mm_heap *heap = zend_mm_get_heap();
// 在 –enable-debug 模式下有效


一句话: 开发期用 enabledebug 跑脚本看退出报告,深查用 Valgrind 或 ASan 定位到行,生产环境监控
memory_get_peak_usage 的趋势,持续涨就是漏了。

赞(0)
未经允许不得转载:171主机测评 » php方案 内存泄漏检测
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址