01
关于unset的一些说法
有人说:
- unset 并不真正释放内存;
- unset 函数只能在变量值占用内存空间超过 256 字节时才会释放内存空间;
- 只有当指向该变量的所有变量(如引用变量)都被销毁后,才会释放内存;
- unset 只是在释放大变量(大量字符串, 大数组)的时候才会真正 free 内存。
02
首先认知 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_ING >”unset”{
RETURN_TOKEN (T_UNSET );
}
另一种途径是从 PHP 官网 unset 获悉:
Note: 因为这是一个语言构造器而不是一个函数,不能被 可变函数 调用。
03
快速了解语言结构与函数的定义和区别
什么是语言结构?
- PHP 关键词;
- PHP 标识符;
- PHP 语言内置的一种语法规则;
什么是函数及包括哪些?
- 一段(一块)代码的集合,可以做某一件事儿的程序;
- 函数分为内部(内置)函数、用户自定义函数、可变函数、匿名函数(闭包函数)。
列举几点两者的区别:
04
正确认识 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(intreal_usage )
{
# ifZEND_MM_STAT
if(real_usage ){
returnAG(mm_heap )->real_size ;
}else{
size_t usage =AG(mm_heap )->size ;
returnusage ;
}
# endif
return0;
}
从源码中看出,memory_get_usage 函数能正常使用需要 ZMM(Zend Memory Manager)支持,如果关闭 ZMM,PHP 内存分配会切换到系统调用 malloc,由于 PHP 不跟踪非 emalloc 分配的内存,此函数会无效,将返回默认值。
提供一种临时关闭 PHP ZMM 方法:
$ exportUSE_ZEND_ALLOC =0
ZMM 默认是开启的,全文皆在开启 ZMM 情况下展开讨论。
05
分析 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 时,并没有实时去系统申请内存。
01
了解 $user 变量构成
<?php
$user=’fanjiapeng’;
- 分配 变量名 内存空间,存入符号表
- 分配 变量值 内存空间
- 在 ZEND_RETURN 阶段,变量名与变量值关联
一个 PHP 变量由两部分组成: 变量名和 变量值。它们的内存大小分配由 ZMM 负责管理。ZMM 是基于 C 的内存函数库做了一层封装,使得 PHP 开发者不用去操心内存管理上的这些事,只需要专注于业务开发就可以啦,简直爽歪歪。
ZMM 是在 php_module_startup 阶段,向系统一次性申请了一大块内存(2MB)。当有新的变量申请内存时,ZMM 直接在余下的内存池中选择合适的大小。当池子不够使用时,再向系统申请新的内存。
关于 ZMM 介绍在这里就不再展开了哟。
02
unset 究竟做了哪些事情?
- 把 变量值 标记为 删除
- 有引用计数的进行相关的处理机制(比如:释放变量值占用的内存)
例1
第一个例子中的变量值其实是一个内部(常量)字符串,存储在 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 大小,所以我们能看到脚本占用内 存发生了变化。
06
unset 总结
本文其实用了较大的篇幅讲了PHP 的内存管理,下面回归正题: unset 究竟会不会释放内存的问题。
笔者分阶段进行了总结:
若开启 ZMM & 达到释放条件时:
- unset 释放小、中变量(small、large),不同于 C/C++ 语言层面上的 free 内存释放。只会把内存归还给 ZMM,不会交还给系统(OS);
- unset 释放大变量(huge),直接释放掉这部分内存;
若关闭 ZMM 时:
- PHP 内存分配会切换到系统调用 malloc / free;
- unset 会直接与系统内存交互,内存利用率低效。
评论前必须登录!
注册