unset()不立即释放内存,因其仅解除变量名与zval绑定;zval仍被其他引用持有时内存不回收,php-fpm进程复用更致跨请求累积,需在正确作用域置null并调用gc_collect_cycles()强制回收。

PHP 7.4 虚拟主机内存不降,不是 unset() 没用,而是你没在对的作用域操作、没处理引用残留、也没触发 GC —— 单靠 unset() 几乎从不立即释放。
为什么 unset($var) 后 memory_get_usage() 不下降
常见错误现象:在某个函数里 unset($rows),但全局的万级数组还在;或者 $obj 被多个变量引用,unset($obj) 只断开当前符号表绑定,zval 还活着。
-
unset()只移除变量名与 zval 的关联,不销毁 zval 本身 - 若该 zval 还被其他变量、对象属性、闭包或全局数组引用,内存不会回收
- 尤其在虚拟主机场景下,PHP-FPM worker 进程复用,未释放的内存会跨请求累积
- 别用
memory_get_usage(false)判断——它返回的是“已用”字节数;要对比真实变化,得用memory_get_usage(true)(申请总量),否则看到的“没降”可能是内存池碎片
PHP 7.4 下真正释放大数组/对象的实操步骤
典型场景:从 MySQL fetchAll() 拿到 5 万行数据,处理完立刻释放。
- 先确保你在定义变量的作用域里操作:不要在函数内
unset($_GLOBALS['big_data']),而要在全局作用域或类方法中显式清理 - 对大数组,优先用
$data = null而非unset($data)——它能切断所有指向该 zval 的引用(前提是无其他变量共享) - 紧接着调用
gc_collect_cycles()强制扫描循环引用(PHP 7.4 默认启用 GC,但不保证及时触发) - 如果数组是对象属性(如
$this->cache),必须在__destruct()或业务结束时显式置null,不能只靠对象销毁
示例:
$rows = $pdo->query("SELECT * FROM huge_table")->fetchAll();
// ... 处理逻辑
$rows = null; // 比 unset() 更可靠
gc_collect_cycles(); // 立即尝试回收
虚拟主机环境下容易被忽略的内存泄漏点
宝塔或 Apache/Nginx + PHP-FPM 部署时,内存问题常藏在配置和进程模型里。
- PHP-FPM 的
pm.max_children设太高,每个子进程都加载了 OPcache 和大配置,总内存爆表——查ps aux | grep php-fpm看实际进程数 - OPcache 未关闭或大小设太大(如
opcache.memory_consumption=512),尤其在多站点共用一个 PHP-FPM pool 时,缓存互相污染 - MySQL 连接未
close(),或 PDO 设置了PDO::ATTR_PERSISTENT => true,连接句柄长期驻留 - 宝塔面板里开着多个 PHP 版本(如 php74、php81、php82),但网站只用 7.4——停掉其余版本可省 100MB+ 内存
- 用
bt stop php74停服务后,仍有php-fpm: pool www进程残留,必须补killall -9 php-fpm或pkill -f "php-fpm.*www"
怎么验证内存真释放了
别信单次 memory_get_usage() 数值,要测趋势。
- 写个压测脚本,用
ab -n 100 -c 10 http://site/test.php模拟多请求,观察free -h中 available 是否回升 - 在脚本开头/结尾加
echo memory_get_peak_usage(true), "\n";,看峰值是否逐轮下降 - 检查 PHP-FPM 日志:
tail -f /www/wwwlogs/php-fpm.log,确认没反复报Allowed memory size exhausted - 最直接:
ps aux --sort=-%mem | head -5,看 php-fpm 进程 RSS 是否回落
真正的释放难点不在代码行,而在引用链是否彻底断裂、GC 是否被触发、以及 FPM 进程是否真的重启干净——这三个环节漏一个,内存就卡在那里不动。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











