【问题标题】:PHP why unset do not makes memory free?PHP 为什么 unset 不能释放内存?
【发布时间】:2026-02-20 13:30:02
【问题描述】:

我有一个 php 应用程序,它应该管理(导出)大量(大量)数据,并且必须在生产环境中完成......所以我需要尽可能降低内存使用量(主要标准)。

简单来说就是App循环导出数据,比如

for($fileCounter=0;$fileCounter<=70;$fileCounter++) {
... HERE a lot of (more than 1K lines) huge work, many variables a lot of DB queries from another databases etc ...
}

我不想在这里展示完整的逻辑,因为对其他人来说可能需要很多时间,这不是这里的重点。

重点是,为什么如果我在每次迭代期间 unset() 所有新创建的变量都不会减少内存使用量?像这样

for($fileCounter=0;$fileCounter<=70;$fileCounter++) {
    // optimization purpose
    $vars_at_start = array_keys(get_defined_vars());
    echo memory_get_peak_usage(true) . PHP_EOL;

... huge logic ...

    $vars_at_end = array_diff($vars_at_start, array_keys(get_defined_vars()));
    foreach($vars_at_end as $v) unset($v);
    unset($vars_at_end);
}

以及如何减少内存使用量?如果我需要使用这么多查询、变量等。

附:代码不是我的:)我也不想从头重写,我只是在寻找优化方向。

没有变量清理内存使用是下一个(它在每次迭代开始时测量)

23592960

Started: 0 - 12:58:26
Ended: 13:00:51
877920256 (difference 854'327'296)

Started: 1 - 13:00:51
Ended: 13:03:39
1559494656 (difference 681'574'400)

和变量清理

23592960

Started: 0 - 12:47:57
Ended: 12:50:20
877920256 (difference 854'327'296)

Started: 1 - 12:50:20
Ended: 12:53:16
1559756800 (difference 681'836'544)

根据我的阅读,PHP 有很多理由泄漏内存......就像这样 https://bugs.php.net/bug.php?id=48781

有一个叫 valgrind 的工具可以帮上忙,去试试吧:)

【问题讨论】:

  • 如果你在 unset 后使用 memory_get_peak_usage(true) 来测试你的内存使用情况,并且它似乎没有下降;那么线索就在PEAK这个词中
  • @MarkBaker 我尝试使用简单的 memory_get_usage() 图片在每次迭代中内存使用量都在增长

标签: php optimization


【解决方案1】:

虽然 unset() 不会释放 PHP 进程消耗的内存,但它会释放它以供 PHP 脚本本身使用。

因此,如果您在循环中创建 10 次大小的 10M 变量并在循环结束时取消设置(或重写)它,那么到最后内存消耗应该低至 10M + 所有其他变量的循环。

如果它增长 - 那么,在您不想显示的完整逻辑中的某处存在泄漏。

【讨论】:

  • 泄漏?嗯..怎么样?例如 ?我应该搜索什么?
【解决方案2】:

因为 PHP 会自动执行此操作。
当不再使用变量时,PHP 会自行取消设置。

【讨论】:

    【解决方案3】:

    因为unset 不会释放内存。它只是释放变量。但是如果那个变量指向某种复杂的结构,那么 PHP 的内存管理无法弄清楚如何释放,它就不会。

    【讨论】:

      【解决方案4】:
      $vars_at_end = array_diff($vars_at_start, array_keys(get_defined_vars()));
      foreach($vars_at_end as $v) unset($v);
      unset($vars_at_end);
      

      整个代码块都在处理变量的副本(甚至是副本的副本),因此会增加大量内存。 您所取消的只是 foreach 循环中的副本。

      您需要取消设置您使用的实际变量

      【讨论】:

      • 大约有 200 个变量,这就是为什么我决定不手动编写它们的原因 :) 但正如大家建议的那样,问题不在于,未设置无济于事
      【解决方案5】:

      顺便说一句,你的 foreach ... unset 循环无论如何都不做。 PHP 使用基于引用的延迟复制写入系统来优化内存使用。这个 foreach 循环这实际上是一个无操作。存储被释放——即返回给内部 Zend Engines emalloc 分配器(不是给操作系统)——或者在任何元素的引用计数为零时重新使用。无论如何,当您离开函数的范围时,对于局部变量和销毁类对象时的类属性都会发生这种情况。克隆变量的浅表副本然后取消设置是没有意义的,因为这只会在引用计数上执行 +1 -1。

      如果您挖掘循环中使用的主要变量的代码并取消设置它们,那么您将真正减少引用计数并降至 0,然后您将释放存储空间以供重用。然而,正如 troelskyn 暗示的那样,现有代码可以通过多种方式为数据元素留下非零引用。经典的方法是,如果您的代码使用引用,那么您可以创建循环引用链,除非循环被显式破坏,否则这些引用链永远不会被回收。即使有一个用于保存结果的全局数组也会占用内存。

      对不起,您的陈述:

      我不想在这里展示完整的逻辑,因为对其他人来说可能需要很多时间,这不是这里的重点。

      错了。在 PHP 中,如果您想了解为什么不将存储返回到内存池中,那么您必须查看代码。

      【讨论】:

      • 感谢您的回答,您能否澄清下一句? “如果你挖掘循环中使用的主要变量的代码并取消设置它们”你能举个小例子吗?
      • 假设您有一个结果数组 $results,其中代码正在聚合数据,您需要取消引用 $results 正在使用的哈希,例如$results = array(); 将在 $results ZVAL 中的哈希上触发 zend_hash_destroy()。 foreach 与 $v=$results; unset($v); 相同,它什么都不做。
      • 这意味着我不能使用 foreach 来取消设置变量,如果我在没有 foreach 帮助的情况下直接在 unset 中以某种方式提及所有变量,它会起作用,对吧?
      • "所有变量" -- 不,你错过了这里的重点。一个标量变量只占用 32 个字节左右,无论如何都会被重用。可能导致这种情况的是哈希表(在对象和数组中)。抱歉,您必须查看代码。
      【解决方案6】:

      unset() 将变量标记为“垃圾收集”

      你试过 __destruct() 吗?

      http://www.php.net/manual/en/language.oop5.decon.php

      【讨论】: