PHP 8.5.7并非官方稳定版,所谓内存泄漏实为8.5-dev分支中JIT或协程场景下的资源滞留问题;需重点审查循环引用、静态缓存、闭包捕获和协程残留四类代码,并用memory_get_usage定位、显式切断引用修复。

一、重点审查这四类高危代码模式
内存泄漏往往不来自语法错误,而源于语义上的“隐式持有”。以下结构需逐行核对:
-
循环引用对象链:如
Class A { public $b; }与Class B { public $a; }相互赋值后未解绑,即使unset($a, $b),GC 也可能因根缓冲区未及时扫描而延迟回收; -
静态缓存无边界增长:例如
private static $cache = [];在循环中持续self::$cache[$key] = $heavyData;,却无容量限制或过期清理机制; -
闭包过度捕获:匿名函数用
use ($bigArray, $dbConn)引入大对象或资源句柄,即使只读取少量字段,整个变量仍被强引用绑定; -
协程上下文残留:Swoole 中
go(function () { ... })内部持有外部对象引用,且协程异常退出未执行unset()或$obj = null,导致对象无法被 GC。
二、用 memory_get_usage 做轻量级定位
无需装扩展,靠内置函数就能快速圈定泄漏点:
- 在脚本入口、循环开始前、关键操作后、释放动作后,分别调用
memory_get_usage(true)(返回真实分配量,非峰值)并记录差值; - 对比连续多次迭代的内存增量——若每次增长固定字节数(如 +12KB),说明存在稳定累积;
- 特别注意:调用
gc_collect_cycles()后内存是否回落?若不回落,大概率是外部引用未断(如全局数组、静态属性、扩展层持有)。
三、修复时优先用“显式切断”而非依赖 GC
PHP 的 GC 是周期性被动机制,不能当作释放保险。主动干预更可靠:
- 对大数组/对象,使用
unset($var)后立即置空引用,如$var = null;; - 打破循环引用:将一方改为
WeakReference::create($obj),并在访问前判空; - 数据库/文件/cURL 资源,务必显式调用
$pdo->close()、fclose($fp)、curl_close($ch); - 静态缓存加管控:引入 LRU 逻辑或按时间/数量淘汰,避免无限追加。
四、JIT 相关泄漏的特殊应对
若确认泄漏发生在启用 opcache.jit=1205 的 CLI/Swoole 场景:
立即学习“PHP免费学习笔记(深入)”;
- 临时规避:设
opcache.jit=0验证是否消失(性能下降但稳定); - 缓冲区限制:将
opcache.jit_buffer_size从默认 256M 降至 64M,延缓泄漏速度; - 避免高频 JIT 触发:重构深度递归函数为迭代,或对闭包做惰性初始化,减少 JIT 编译频次。



















