解析PHP中的unset究竟会不会释放内存

叶初
叶初
发布时间:2026-10-14| 2人浏览过
原创

许多开发者发现调用 unset 后 memory_get_usage() 的返回值并未显著下降,从而质疑 PHP 的内存释放机制。本文通过分析底层内存分配原理、符号表开销及数组扩容特性,揭示 unset 的真实行为及其对内存监控数据的影响。

表象与真相:内存数据的错觉

在探讨具体机制前,让我们先看一个典型的代码示例:

var_dump(memory_get_usage());
$a = "laruence";
var_dump(memory_get_usage());
unset($a);
var_dump(memory_get_usage());

在实际运行中(具体数值因系统环境和 PHP 版本而异),我们可能会观察到如下序列:

  • 初始内存:90440
  • 赋值后:90640
  • Unset 后:90472

注意到 90472 - 90440 = 32。这多出的 32 字节让部分结论产生了分歧:有人说 PHP 的 unset 不释放内存,有人则认为是大对象才触发释放,甚至有观点认为在 PHP 层面讨论绝对内存意义不大。但这 32 个字节究竟去了哪里?unset 到底做了什么?

内存分配的两个关键认知

第1步:打破“显式分配”的思维定势

与 C 语言不同,PHP 中许多内存分配是隐式的。以 $a = "laruence" 为例,不仅仅是字符串值本身占用了内存,还包含:
1. 为变量名 $a 分配内存并写入符号表。
2. 为变量值(zval 结构体及实际字符)分配内存。

因此,单纯看变量值的增删无法反映完整的内存变化。

第2步:理解 PHP 的内存管理 API

PHP 的 unset 确实会释放由该变量占用的内存资源(遵循引用计数和变量分离原则),但这种释放并非直接交还给操作系统(OS)。

PHP 内部使用类似 emalloc/efree 的 API 管理内存:
- emalloc:当需要内存时,PHP 往往不是向 OS 申请单个小块,而是批量申请一大块内存区域(内存池),从中切割出一部分给当前逻辑。这样避免了频繁的系统调用开销。
- efree:释放时,PHP 不会归还给 OS,而是将该内存块放入内部的“空闲内存列表”或缓存中,供下一次请求复用。

为什么 memory_get_usage 看起来没变?

第1步:区分 Real Size 与 Usage

要验证上述理论,需使用 memory_get_usage(TRUE) 获取进程向 OS 申请的总真实内存(Real Size),而非 PHP 内部已分配但未释放给 OS 的大小。

<?php
var_dump(memory_get_usage(TRUE)); // 获取 real_size
$a = "laruence";
var_dump(memory_get_usage(TRUE));
unset($a);
var_dump(memory_get_usage(TRUE));
?>

输出可能如下:

  • 262144
  • 262144
  • 262144

可见,定义变量和释放变量时,进程向 OS 申请的物理内存总量并未变化。这证实了 PHP 仅在内部复用内存池,未发生页释放。

第2步:分析那 32 字节的去向

回到最初的疑问:为什么 memory_get_usage() 显示少了 32 字节?关键在于那些看不见的隐式分配。例如:

<?php
var_dump("I am www.jb51.net"); // 触发了输出缓冲等隐性操作
var_dump(memory_get_usage());
$a = "laruence";
var_dump(memory_get_usage());
unset($a);
var_dump(memory_get_usage());
?>

输出可能为:

  • 90808 (print 后)
  • 90976 (赋值后)
  • 90808 (unset 后)

此时内存完全回退。之前的 32 字节差异,往往是因为在执行 unset 前后,脚本中其他隐性操作(如 echo 输出的 Header、临时缓冲区等)造成的干扰。严格来说,unset 清理的是自身关联的资源。

特殊场景:只能增不能减的数组

对于关联数组(底层为 Hashtable),内存释放存在特殊情况。PHP 的哈希表在初始化时只分配少量空间,随着元素增加会动态扩容(Resize)。但哈希表一旦扩容,其底层内存块数量只会增加,不会减少。

假设代码逻辑如下:

  • 创建 100 个变量并加入全局符号表(本质也是 Hashtable)。
  • 逐一 unset 这些变量。

结果对比:

  • 插入前:约 93560
  • 插入后:约 118848(触发了扩容)
  • Unset 后:约 104448

虽然变量占用的具体 zval 内存被释放(从 118848 降至 104448),但并未回到初始的 93560。原因是符号表的容量已经扩大以适应 100 个元素,unset 只是清空了键值对,保留了哈希表的结构骨架。

总结

PHP 的 unset 会释放变量持有的资源(如字符串内容、数组条目、对象引用计数归零时的回收),但其返回给系统的内存表现受以下因素影响:
1. 内存池机制:小块内存通常在内部复用,不会立即返还 OS。
2. 符号表与 Hashtable:容器结构的扩容具有单向性,清空元素不缩小容器体积。
3. 隐式开销:执行栈、输出缓冲等隐性分配的波动会干扰监控数据。

理解这套机制有助于更客观地看待 PHP 的内存消耗,在进行性能调优时,关注大数组的处理、循环中的内存累积以及正确的资源关闭才是关键所在。

以上就是关于 PHP unset 内存释放行为的详细内容,更多关于 PHP 内存管理与优化的资料请关注本站其它相关文章!

相关标签:

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。