HarmonyOS开发者限时福利来啦!最高10w+现金激励等你拿~ 了解详情
写点什么

深入理解 PHP7 unset 真的会释放内存吗?

  • 2019-11-14
  • 本文字数:2971 字

    阅读完需:约 10 分钟

深入理解 PHP7 unset 真的会释放内存吗?

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

1 关于 unset 的一些说法

有人说:


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

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

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

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

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

验证方法之一

$ php -r "var_dump(function_exists('unset'));"bool(false)
复制代码

验证方法之二

$ php --rf  unsetException: 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 -vPHP 7.3.5 (cli) (built: May 27 2019 20:59:34) ( NTS DEBUG )Copyright (c) 1997-2018 The PHP GroupZend 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() 小字符串变量例子:


<?phpvar_dump(memory_get_usage());$user = 'fanjiapeng';var_dump(memory_get_usage());unset($user);var_dump(memory_get_usage());
复制代码


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


$ php small_string_a.phpint(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() 之后内存占用发生了变化:


<?phpvar_dump(memory_get_usage());$user = 'fanjiapeng' . time();var_dump(memory_get_usage());unset($user);var_dump(memory_get_usage());
复制代码


在 CLI 模式下执行:


$ php small_string_b.phpint(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() 大字符串变量例子:


<?phpvar_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.phpint(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 云计算(ID:hulktalk)。


原文链接:


https://mp.weixin.qq.com/s/XIuto7yzBwr7cCiws_kFUw


2019-11-14 15:402498

评论

发布
暂无评论
发现更多内容

IM跨平台技术学习(五):融云基于Electron的IM跨平台SDK改造实践总结

JackJiang

Qt|使用QDialog窗口调用exec时崩溃解决问题

中国好公民st

c++ qt 10月月更

Qt | QList的removeAt、takeAt的区别

YOLO.

qt 10月月更 C++

uniapp实现国际化多语言切换

源字节1号

软件开发

以开发之名|斗罗大陆:创造一个尽情探险的开放式游戏世界

HarmonyOS SDK

华为 HMS Core

C# TreeView控件方法属性学习

IC00

C# 学习 程序员 上位机 10月月更

如何在 SAP Business Application Studio 里创建 SAP UI5 应用并部署到 BTP 平台上

汪子熙

云原生 云平台 SAP 10月月更

跑批为什么这么难

陈橘又青

Windows Server 2008 R2将tomcat添加进系统服务

我爱娃哈哈😍

tomcat windows 服务器运维

软件测试校招面试真题 | 面试官必问面试题之你有什么想问我的?

测试人

Linux 各个Namespace 的作用

忙着长大#

linux namespace

校招面试真题 | 和面试官聊的很 high,但就是拿不到 offer,怎么办?

霍格沃兹测试开发学社

观测云获亚马逊云科技年度 DevOps 合作伙伴奖

观测云

火力全开-Precision 7670触顶性能天花版

科技热闻

峰会回顾 | 基于StarRocks,百草味如何通过数据赋能快消品行业

StarRocks

#数据库

小白必看——台式机选购指南

科技热闻

嵌入式 Linux 入门(一、Linux 基本介绍及文件结构)

矜辰所致

Linux 10月月更 文件结构

EMQ走进富士康,探讨新生态模式下智能制造与智能汽车基础架构的未来

EMQ映云科技

车联网 物联网 IoT emqx 10月月更

CEF | CEF浏览器客户端功能扩展:实现与JS交互的功能

YOLO.

qt 10月月更 C++

云管理工具是干什么的?有哪些?哪个品牌好?

行云管家

云计算 云管理 云工具

区服分析丨更透彻的游戏营运数据解读,助力高效增长

HarmonyOS SDK

分析

数据结构学习,稀疏矩阵(三元组和十字链)

IC00

学习 数据结构 算法 学习笔记 10月月更

OpenYurt v1.0 正式发布!一文了解三大社区 SIG 重点更新

阿里巴巴云原生

阿里云 开源 云原生 openyurt 版本升级

好文分享 | 记一次Oracle12c数据库SQL短暂缓慢问题分析

墨天轮

数据库 oracle 性能优化 征文投稿

技术贴 | 走进 PostgreSQL 行级安全策略

KaiwuDB

时序 #数据库

StartDT奇点云邀您参加2022云栖大会,11月3-5日杭州见

奇点云

云栖大会 奇点云

Kubernetes 安全专家(CKS)考试技巧

HummerCloud

Kubernetes 云原生 考试经验 CKS 10月月更

【云安全】云安全产品有哪些?哪个品牌好?

行云管家

云计算 网络安全 安全 云安全

如何利用Java在Word中创建表格

Geek_249eec

Java word 表格

训练营 | 如何成为一名开源社区贡献者?

阿里巴巴云原生

阿里云 开源 云原生 KubeVela

MASA MAUI Plugin (四)条形码、二维码扫描功能

MASA技术团队

MASA MAUI MASA Blazor

深入理解 PHP7 unset 真的会释放内存吗?_文化 & 方法_范家鹏_InfoQ精选文章