本文目录导读:

PHP 内存泄漏排查是一个系统性的过程,需要结合工具、代码审查和监控,以下是一套完整的排查指南:
理解 PHP 内存模型
首先明确:PHP 的“内存泄漏”通常是“内存峰值过高”,因为 PHP-FPM 每个请求结束后会释放大部分内存(进程回收),真正的问题往往出现在:
- 长驻进程(如 swoole、workerman、队列消费者)
- 单次请求处理数据量过大
- 循环引用未清理
- 全局变量/静态变量累积
定位泄漏的 6 个步骤
步骤1:确认是否存在泄漏
# 监控单进程内存(持续观察)
while true; do ps -o rss,pmem,cmd -p $(pgrep -f "php-fpm: pool" | head -1); sleep 5; done
# 批量查看所有 PHP 进程内存
ps aux | grep php | awk '{print $2, $6/1024"MB", $11}'
步骤2:启用内存检测工具
Xdebug + 内存分析:
// 在脚本中分段记录 $usage = memory_get_usage(); // ...业务代码... echo "操作A后: " . (memory_get_usage() - $usage) . " bytes\n";
使用 PHP 内置函数定位:
class MemoryTracker {
private static $markers = [];
public static function mark($label) {
self::$markers[$label] = [
'usage' => memory_get_usage(true),
'peak' => memory_get_peak_usage(true),
'time' => microtime(true)
];
}
public static function report() {
$prev = null;
foreach (self::$markers as $label => $data) {
$diff = $prev ? $data['usage'] - $prev['usage'] : 0;
echo "$label: 使用{$data['usage']} bytes, 增长{$diff} bytes\n";
$prev = $data;
}
}
}
步骤3:使用 Valgrind 定位 C 层面泄漏
# 安装
apt-get install valgrind
# 运行 PHP 脚本(需编译 PHP 时支持)
valgrind --tool=memcheck --leak-check=full \
--show-leak-kinds=all \
--log-file=valgrind.log \
php your_script.php
步骤4:检查 PHP 扩展泄漏
# 逐个禁用扩展测试 php -d extension_dir=/usr/lib/php/20210902 -n your_script.php # 通过 php-fpm 日志确认 php-fpm -t 2>&1 | grep -i memory
步骤5:代码审查重点区域
// 高危模式1:静态/全局变量累积
class Cache {
private static $items = []; // 永不释放
public static function set($key, $value) {
self::$items[$key] = $value; // 如果无限增长,就是泄漏
}
}
// 高危模式2:大数据量循环处理
$data = DB::select("SELECT * FROM huge_table");
foreach ($data as $row) {
// 处理但未清除引用
}
// 高危模式3:闭包捕获
$result = [];
for ($i = 0; $i < 100000; $i++) {
$closure = function() use ($result) { // 捕获整个 $result
// 逻辑...
};
$result[] = $closure; // 每个闭包都带一份 $result 引用
}
// 高危模式4:长连接未复用
foreach ($items as $item) {
$conn = new PDO(...); // 每次新建连接
// 用完未关闭/释放
}
步骤6:专门工具分析
Blackfire.io(可视化分析)
# 安装黑火监控
# 在代码中添加埋点
blackfire_set_transaction_name("check_memory");
// 业务代码...
blackfire_end_transaction();
Tideways(生产环境分析)
自定义简单监控脚本:
// 每 100 次迭代检查一次
$startMemory = memory_get_usage();
for ($i = 0; $i < 1000000; $i++) {
processItem($i);
if ($i % 1000 == 0) {
$current = memory_get_usage();
$diff = $current - $startMemory;
if ($diff > 10 * 1024 * 1024) { // 超过 10MB
echo "可能的泄漏在迭代 $i: {$diff} bytes\n";
debug_print_backtrace();
}
}
}
常见泄漏场景与修复方案
场景1:循环引用导致泄漏
// 问题代码
class A { public $b; }
class B { public $a; }
$a = new A();
$b = new B();
$a->b = $b;
$b->a = $a; // 循环引用
// 修复:使用弱引用(PHP 7.4+)
$a->b = WeakReference::create($b);
场景2:Generator/Iterator 未销毁
// 问题代码
$generator = getHugeData(); // 返回 generator
foreach ($generator as $item) {
// 中途跳出循环
if ($condition) break; // generator 被保留,资源未释放
}
// 修复:手动调用 close
finally {
$generator->close();
}
场景3:文件/资源句柄未关闭
// 问题代码
foreach ($files as $file) {
$handle = fopen($file);
// 忘记 fclose()
}
// 修复:使用 try-finally 或 using 块
foreach ($files as $file) {
$handle = fopen($file);
try {
// 处理逻辑
} finally {
fclose($handle);
}
// 或者使用自动关闭的包装器
$content = file_get_contents($file); // 自动关闭
}
生产环境监控方案
添加内存监控指标到应用
// 在应用框架的 after-middleware/事件中
class MemoryMonitor {
public function __construct() {
$this->baseline = memory_get_usage();
}
public function __destruct() {
$used = memory_get_peak_usage() - $this->baseline;
if ($used > 50 * 1024 * 1024) { // 50MB 告警
error_log("高内存使用: " . formatBytes($used));
}
}
}
使用云监控工具
# Prometheus + Grafana 配置示例
php_memory_usage_bytes{process="php-fpm"}
php_memory_peak_usage_bytes{process="php-fpm"}
预防泄漏的最佳实践
- 使用依赖注入容器而不是全局/静态实例
- 避免循环引用:序列化数组时使用
serialize/unserialize断开引用 - 大数据量操作分页:不要一次加载全部数据
- 定期重启长驻进程:设置合适的内存上限自动重启
- 代码规范检查:使用 PHPStan/Psalm 静态分析工具
快速诊断命令汇总
# 快速查看最大内存占用的 PHP 进程
ps aux --sort=-rss | grep php | head
# PHP 脚本中实时监控
php -r 'echo ini_get("memory_limit");'
# 查看是否有未释放的 MySQL/Redis 连接
ss -tlnp | grep php-fpm
# 使用 PHP7+ 的内置检测
php -d display_errors=1 -d error_reporting=E_ALL -d opcache.enable=0 -d `php -d opcache.enable_cli=1 -d opcache.validate_timestamps=0` -r 'echo memory_get_usage();'
总结排查流程
确认内存持续增长(观察多次请求/循环内存变化)
2. 使用 memory_get_usage 分段定位(二分法找泄漏点)
3. 结合 Valgrind 排除扩展问题
4. 代码审查 + xdebug profile 分析
5. 实施修复并验证(监控回归)
关键点:先确认是不是真泄漏,再定位泄漏位置,最后修复和验证,工具只是辅助,最重要的还是理解代码中对内存引用的生命周期管理。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播