女主宣言

PHP 提供了 unset 用于释放指定的变量,那么它真的会释放内存吗?本文将从这个话题展开讨论。

本文作者范家鹏,360技术委员会--WEB服务端分TC委员。

文本转载自360技术,转载请注明出处。

PS:丰富的一线技术、多元化的表现形式,尽在“360云计算”,点关注哦!

1

关于unset的一些说法

有人说:

  • unset() 并不真正释放内存;

  • unset() 函数只能在变量值占用内存空间超过 256 字节时才会释放内存空间;

  • 只有当指向该变量的所有变量(如引用变量)都被销毁后,才会释放内存;

  • unset() 只是在释放大变量(大量字符串, 大数组)的时候才会真正 free 内存。

2

首先认知unset真的是函数吗?

验证方法之一

$ php -r "var_dump(function_exists('unset'));"
bool(false)

验证方法之二

$ php --rf  unset
Exception: Function unset() does not exist

上面提到的两种检验方法,实际上是不严谨的,比如函数不存在时,会出现相同的输出结果。所以我们在使用时,需要开发人员合理判断当前的使用场景。

那么有没有一种准确的 判断呢?答案一定是有的。

一种途径是从 PHP 源码入手:

Zend/zend_language_scanner.l 找到语法规则:

<ST_IN_SCRIPTING>"unset" {  RETURN_TOKEN(T_UNSET);
}

另一种途径是从 PHP 官网 unset() 获悉:

Note: 因为这是一个语言构造器而不是一个函数,不能被 可变函数 调用。

3

快速了解语言结构与函数的定义和区别

什么是语言结构?

  • PHP 关键词;

  • PHP 标识符;

  • PHP 语言内置的一种语法规则;

什么是函数及包括哪些?

  • 一段(一块)代码的集合,可以做某一件事儿的程序;

  • 函数分为内部(内置)函数、用户自定义函数、可变函数、匿名函数(闭包函数)。

列举几点两者的区别:

4

正确认识 memory_get_usage 函数

PHP 函数原型如下:

memory_get_usage ([ bool $real_usage = false ] ) : int

  • 当 $real_usage 为 false 时,返回当前申请的已经使用的内存大小;

  • 当 $real_usage 为 true 时,返回当前申请的的内存大小,包括已使用和未使用内存;

函数实现 C 源码如下:

ZEND_API size_t zend_memory_usage(int real_usage)
{
#if ZEND_MM_STAT    if (real_usage) {   return AG(mm_heap)->real_size;   } else {    size_t usage = AG(mm_heap)->size;   return usage;   }
#endif  return 0;
}

从源码中看出,memory_get_usage() 函数能正常使用需要 ZMM(Zend Memory Manager)支持,如果关闭 ZMM,PHP 内存分配会切换到系统调用 malloc(),由于 PHP 不跟踪非 emalloc() 分配的内存,此函数会无效,将返回默认值。

提供一种临时关闭 PHP ZMM 方法:

$ export USE_ZEND_ALLOC=0

ZMM 默认是开启的,全文皆在开启 ZMM 情况下展开讨论。

5

分析unset字符串变量例子

PHP 环境信息如下:

$ php -v
PHP 7.3.5 (cli) (built: May 27 2019 20:59:34) ( NTS DEBUG )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.3.5, Copyright (c) 1998-2018 Zend Technologies   with Zend OPcache v7.3.5, Copyright (c) 1999-2018, by Zend Technologies

例1

一个 unset() 小字符串变量例子:

<?php
var_dump(memory_get_usage());
$user = 'fanjiapeng';
var_dump(memory_get_usage());
unset($user);
var_dump(memory_get_usage());

在 CLI 模式下执行,输出的数字大小取决于你的环境:

$ php small_string_a.php
int(410064)
int(410128)
int(410128)

这里抛出了一个问题,unset() 之后脚本占用内存空间没有减小呢?

如果我们微调下当前例子,调用 memory_get_usage(true) 函数测试,得到如下输出结果:

int(2097152)
int(2097152)
int(2097152)

Why? 这是因为 PHP 采用的是预分配内存策略,在定义一个变量 $user 时,并没有实时去系统申请内存。

了解$user变量构成

<?php
$user = 'fanjiapeng';

  1. 分配 变量名 内存空间,存入符号表

  2. 分配 变量值 内存空间

  3. 在 ZEND_RETURN 阶段,变量名与变量值关联

一个 PHP 变量由两部分组成:变量名 和 变量值。它们的内存大小分配由 ZMM 负责管理。ZMM 是基于 C 的内存函数库做了一层封装,使得 PHP 开发者不用去操心内存管理上的这些事,只需要专注于业务开发就可以啦,简直爽歪歪。

ZMM 是在 php_module_startup 阶段,向系统一次性申请了一大块内存(2MB)。当有新的变量申请内存时,ZMM 直接在余下的内存池中选择合适的大小。当池子不够使用时,再向系统申请新的内存。

关于 ZMM 介绍在这里就不再展开了哟。

unset究竟做了哪些事情?

  1. 把 变量值 标记为 删除

  2. 有引用计数的进行相关的处理机制(比如:释放变量值占用的内存)

这个例子中的变量值其实是一个内部(常量)字符串,存储在 interned_strings 哈希表 中。它不需要通过引用计数机制来管理,unset() 也不会去释放它。既然变量不会被释放,那么也就不会存在有回收。依据 memory_get_usage() 函数说明,所以我们才会看到, unset() 之后内存占用大小无变化。

那么内部字符串(interned_strings)是在什么时候释放呢?

关闭 Opcache 时(NTS):

开启 Opcache 时(NTS):

例2

来看另一个例子,unset() 之后内存占用发生了变化:

<?php
var_dump(memory_get_usage());
$user = 'fanjiapeng' . time();
var_dump(memory_get_usage());
unset($user);
var_dump(memory_get_usage());

在 CLI 模式下执行:

$ php small_string_b.php
int(410208)
int(410352)
int(410272)

如果微调一下代码,得到的结果与第一个例子是相同的:

// var_dump(memory_get_usage(true));
int(2097152)
int(2097152)
int(2097152)

但是第二个例子中的变量值是临时字符串(IS_TMP_VAR),zval 关键信息如下:

(zval).u1.v.type_flags == 1
(zval).value.counted.gc.refcount == 1

若是这类变量,unset() 直接就释放掉了这部分内存,脚本的实际内存占用值会被减少。由于当前变量值占用内存小于3072B,属于 small 内存管辖范围,被释放的这部分内存会归还到空闲的内存列表中(ZMM),不会交还给系统。

若 refcount 大于1,则引用计数减1,然后进入 PHP 垃圾收集器处理机制。

例3

再来看一个 unset() 大字符串变量例子:

<?php
var_dump(memory_get_usage(true));
$user = file_get_contents('/tmp/big_string.log'); // 7845566 B
var_dump(memory_get_usage(true));
unset($user);
var_dump(memory_get_usage(true));

在 CLI 模式下执行:

$ php huge_memory.php
int(2097152)
int(9945088)
int(2097152)

第三个例子中的变量值是临时字符串(IS_VAR),zval 关键信息同上,它们的释放机制也是同理的。

由于当前申请的内存大于 2044 KB,属于 huge 内存管辖范围。由 zend_mm_huge_list 大内存链表结构来管理,是通过 PHP zend_mm_alloc_huge() 函数申请 size 大小内存, 最终调用 Linux mmap() 函数来向操作系统申请内存。

unset() 最终调用 Linux munmap() 函数解除内存映射关系,同时 AG(mm_heap)>real_size 和 AG(mm_heap)->size 减去相应的 size 大小,所以我们能看到脚本占用内 存发生了变化。

6

unset总结

本文其实用了较大的篇幅讲了PHP 的内存管理,下面回归正题:unset() 究竟会不会释放内存的问题。

笔者分阶段进行了总结:

若开启 ZMM & 达到释放条件时:

  • unset() 释放小、中变量(small、large),不同于 C/C++ 语言层面上的 free() 内存释放。只会把内存归还给 ZMM,不会交还给系统(OS);

  • unset() 释放大变量(huge),直接释放掉这部分内存;

若关闭 ZMM 时:

  • PHP 内存分配会切换到系统调用 malloc() / free();

  • unset() 会直接与系统内存交互,内存利用率低效。

360云计算

由360云平台团队打造的技术分享公众号,内容涉及数据库、大数据、微服务、容器、AIOps、IoT等众多技术领域,通过夯实的技术积累和丰富的一线实战经验,为你带来最有料的技术分享

深入理解 PHP7 unset 真的会释放内存吗?相关推荐

  1. php7 unset函数,深入理解 PHP7 unset 真的会释放内存吗?

    一.关于unset的一些说法 有人说:unset() 并不真正释放内存: unset() 函数只能在变量值占用内存空间超过 256 字节时才会释放内存空间: 只有当指向该变量的所有变量(如引用变量)都 ...

  2. PHP unset()函数销毁变量 但没有实现释放内存

    <?PHP $a = "hello";$b = &$a;unset( $b );echo $a; // 输出 helloecho $b; // 报错$b = &quo ...

  3. PHP如何让内存不释放,PHP中使用unset销毁变量并内存释放问题

    代码如下: for ( $i = 1; $i < 100; $i++ ) { $str = str_repeat('01234567', $i); $a = memory_get_usage() ...

  4. PHP如何释放内存之unset销毁变量并释放内存详解

    PHP的unset()函数用来清除.销毁变量,不用的变量,我们可以用unset()将它销毁.但是某些时候,用unset()却无法达到销毁变量占用的内存!我们先看一个例子: <?php $s = ...

  5. android图片释放内存吗,Android手机真的需要经常清理内存吗

    Android手机需要清内存吗?需要吗?需要吗?需要吗?结论是再明显不过:不需要!其实这个结论也适用iPhone手机,iOS在内存管理方面更为激进. 很长一段时间以来,人们通过以下的办法清理手机内存 ...

  6. php zval_copy_static_var(),深入理解 PHP7 中全新的 zval 容器和引用计数机制

    深入理解 PHP7 中全新的 zval 容器和引用计数机制2020-06-14 13:11:54 最近在查阅 PHP7 垃圾回收的资料的时候,网上的一些代码示例在本地环境下运行时出现了不同的结果,使我 ...

  7. 深入理解 PHP7 中全新的 zval 容器和引用计数机制

    深入理解 PHP7 中全新的 zval 容器和引用计数机制 最近在查阅 PHP7 垃圾回收的资料的时候,网上的一些代码示例在本地环境下运行时出现了不同的结果,使我一度非常迷惑. 仔细一想不难发现问题所 ...

  8. php的内存划分,解析PHP中的内存管理,PHP动态分配和释放内存

    摘要 内存管理对于长期运行的程序,例如服务器守护程序,是相当重要的影响:因此,理解PHP是如何分配与释放内存的对于创建这类程序极为重要.本文将重点探讨PHP的内存管理问题. 一. 内存 在PHP中,填 ...

  9. 利用二叉树的思想来实现分配和释放内存方法

    虽然大部分系统都有提供内存动态分配和释放函数(即C语言中的malloc和free函数),但是在嵌入式开发中由于系统的限制往往需要自己来实现内存管理,如在有些平台上可动态申请的最大空间不能满足程序设计的 ...

最新文章

  1. altium designer 的Import wizard 没有内容或为空的解决方法
  2. RHEL6入门系列之十三,阶段练习1
  3. python的shutil模块是内置的_Python之shutil模块11个常用函数详解,python内置函数是什么...
  4. Android 编程下 AlarmManager
  5. TeaTalk·Online 演讲实录 | 圆满完结 大数据+云原生,再度风云起
  6. prthon序列、映射
  7. 未能将“C:\Program Files (x86)\DevExpress 2009.2\Components\Sources\DevExpress.DLL\DevExpress.XtraGrid.v
  8. 计算机专业人员有补贴吗,2020年各地软考证书申领补贴需要什么条件
  9. 基于FusionInsight Manager的大数据架构图
  10. c语言程序设计精髓第五章编程题
  11. 什么是浏览器?2、常见的主流浏览器及其内核?什么是服务器?(学习笔记)
  12. android 最好的gtd软件,这9款高质量APP让你工作效率提高50%!
  13. Python中sorted()排序与字母大小写的问题
  14. Word2019表格文字一直无法上下对齐的解法
  15. 数字图像处理学习笔记4第四章 图像变换 附实验
  16. ESL第十章 提升和加性树 AdaBoost、向前分段【加性模型】、指数损失合理性、边缘/鲁棒性/平方合页损失/M回归、长尾偏度/现成方法、GBDT、方差分析、随机梯度提升、相对重要性/偏相依图
  17. 美国芯片陆续转向,或许为当初的做法后悔不迭,芯片补贴也难改局面
  18. element表格分页功能
  19. 12个优雅的 python 代码使用案例
  20. 移动平台开发工程训练

热门文章

  1. fabric node enrollAdmin.js 报错SyntaxError: Unexpected token function at createScript (vm.js:56:10)
  2. 高级JAVA - 动态代理的实现原理和源码分析
  3. maven的聚合和继承详解(2021版)
  4. 无法解析 uafxcw.lib_二级建造师《实务科目》推荐知识点习题,附中业网校答案解析...
  5. JavaScript——面向对象之继承(原型对象)与多态(重载、重写)
  6. sql年月日加减法,计算两个日期之间的天数
  7. vuedraggable嵌套块拖拽_Vue 基于 vuedraggable 实现选中、拖拽、排序效果
  8. python爬pdf的曲线_科学网—Python爬PDF - 胡鹏程的博文
  9. Mysql 获取年级每个班前十学生的信息
  10. 个人作业-Week1