不能直接清掉 $globals 里的变量,unset($globals['foo']) 仅断开当前作用域引用,不销毁变量本身;真正释放需切断所有引用链并处理资源。

unset() 能清掉 $GLOBALS 里的变量吗
不能直接清掉 —— unset() 对 $GLOBALS 数组本身或它的键做操作时,行为和你直觉可能不一样。比如 unset($GLOBALS['foo']) 看似删了,但实际只是断开了当前作用域对那个全局变量的引用,原变量在其他作用域(比如函数内用 global $foo 声明后)仍可读写。
真正想“清理”,得确认变量是否还在内存里被其他地方持有。常见误判是:执行了 unset($GLOBALS['config']) 就以为安全了,结果某个类的静态属性还存着它的引用,或者 $_SESSION 里序列化过它。
-
unset($var)和unset($GLOBALS['var'])效果等价,但都不影响已存在的引用 - 如果变量值是对象,要检查是否有
__destruct()逻辑依赖它,提前 unset 可能导致后续调用出错 - CLI 模式下长期运行的脚本(如 Worker),反复 unset 后不重置
$GLOBALS键,可能导致数组膨胀(PHP 7.4+ 有优化,但老版本要注意)
想彻底释放全局变量内存,该怎么做
PHP 的全局变量本质是符号表条目,释放内存的关键不是“删名字”,而是让变量值的引用计数归零。所以重点在切断所有引用链。
实操上分三步走:
立即学习“PHP免费学习笔记(深入)”;
- 显式
unset()所有已知变量名,包括通过global声明过的、以及$GLOBALS中列出的 - 检查是否有闭包使用
use捕获了这些变量(尤其是匿名函数赋值给静态属性或全局常量时) - 如果变量值是资源(如
fopen()返回的句柄)、PDO 实例或 GD 图像,必须先调用对应释放函数(fclose()、$pdo = null、imagedestroy()),否则unset()不会触发底层清理
示例:清理一个被多处引用的配置数组
$_CONFIG = ['db' => [...], 'cache' => [...]];
$workerConfig = &$_CONFIG; // 引用赋值
function init() { global $_CONFIG; }
// 正确清理顺序:
imagedestroy($GLOBALS['tmp_img'] ?? null); // 先处理资源
$workerConfig = null; // 断开别名引用
unset($_CONFIG); // 最后删主变量
为什么 unset($GLOBALS) 会报错
因为 $GLOBALS 是 PHP 内部超全局数组,它不是普通变量,而是一个指向当前符号表的只读代理。尝试 unset($GLOBALS) 会触发致命错误:Fatal error: Cannot unset $GLOBALS。
这个限制从 PHP 4.2 就存在,目的是防止破坏运行时环境。哪怕你在 eval 或反射中试图绕过,也会被 Zend 引擎拦截。
- 不要写
unset($GLOBALS),也不要用$GLOBALS = []—— 后者虽然不报错,但只是覆盖了数组内容,原符号表条目还在 - 想“重置”全局空间?唯一可靠方式是重启请求(Web 场景)或
exit后重新include(CLI 场景),别硬清 - 某些框架(如 Laravel)的
app()->flush()看似清全局,其实清的是服务容器,和$GLOBALS无关
CLI 长进程里反复 require 导致全局变量堆积怎么办
这是真实高频问题:Worker 循环中 require 'config.php',每次都会把新变量塞进 $GLOBALS,久而久之内存只增不减。根本原因不是没 unset,而是每次 require 都新建符号表条目,旧的还没被回收。
解决方案不是事后清理,而是从加载机制上规避:
- 改用
include_once或require_once,但注意它们只按文件路径去重,如果 config.php 里用了动态路径拼接,依然会重复加载 - 把配置封装成函数返回,而不是裸变量:用
return ['db' => ...]+$cfg = require 'config.php';,避免污染全局空间 - 真需要动态重载时,手动维护一个配置缓存变量,加载前先
unset()旧值,并确保没有其他变量引用它
最容易被忽略的一点:PHP 的 opcache.revalidate_freq 在 CLI 下默认为 0,意味着即使你改了 config.php 文件,require 还是读缓存 —— 看似没加载,其实是 opcache 捂着旧变量不放。











