iis服务器助手广告广告
返回顶部
首页 > 资讯 > 后端开发 > PHP编程 >PHP垃圾回收及内存管理相关内容有哪些
  • 740
分享到

PHP垃圾回收及内存管理相关内容有哪些

2023-06-21 21:06:55 740人浏览 独家记忆
摘要

这篇文章主要讲解了“PHP垃圾回收及内存管理相关内容有哪些”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“php垃圾回收及内存管理相关内容有哪些”吧!PHP 垃圾回收与内存管理指引本文将要讲述

这篇文章主要讲解了“PHP垃圾回收及内存管理相关内容有哪些”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“php垃圾回收及内存管理相关内容有哪些”吧!

本文将要讲述 PHP 发展历程中的垃圾回收及内存管理相关内容。

引用计数

在 PHP 5.2 及以前的版本中,PHP 的垃圾回收采用的是 引用计数 算法

引用计数基础知识

引用计数基础知识

php 的变量存储在「zval」变量容器数据结构)中,「zval」属性包含如下信息:

  • 当前变量的数据类型;

  • 当前变量的值;

  • 用于标识变量是否为引用传递的 is_ref 布尔类型标识;

  • 指向该「zval」变量容器的变量个数的 refcount 标识符(即这个 zval 被引用的次数,注意这里的引用不是指引用传值,注意区分)。

当一个变量被赋值时,就会生成一个对应的「zavl」变量容器。

查看变量 zval 容器信息

要查看变量的「zval」容器信息(即查看变量的 is_ref 和 refcount),可以使用 XDebug 调试工具xdebug_debug_zval() 函数。

假设,我们已经成功安装好 XDebug 工具,现在就可以来对变量进行调试了。

  • 查看普通变量的 zval 信息

如果我们的 PHP 语句只是对变量进行简单赋值时,is_ref 标识值为 0,refcount 值为 1;若将这个变量作为值赋值给另一个变量时,则增加 zval 变量容器的 refcount 计数;同理,销毁(unset)变量时,「refcount」相应的减去 1。

请看下面的示例:

<?php// 变量赋值时,refcount 值等于 1$name = 'liuGongzi';xdebug_debug_zval('name'); // (refcount=1, is_ref=0)string 'liugongzi' (length=9)// $name 作为值赋值给另一个变量, refcount 值增加 1$copy = $name;xdebug_debug_zval('name'); // (refcount=2, is_ref=0)string 'liugongzi' (length=9)// 销毁变量,refcount 值减掉 1unset($copy);xdebug_debug_zval('name'); // (refcount=1, is_ref=0)string 'liugongzi' (length=9)
  • 写时复制

写时复制(Copy On Write:COW),简单描述为:如果通过赋值的方式赋值给变量时不会申请新内存来存放新变量所保存的值,而是简单的通过一个计数器来共用内存,只有在其中的一个引用指向变量的值发生变化时,才申请新空间来保存值内容以减少对内存的占用。 - TPIP 写时复制

通过前面的简单变量的 zval 信息我们知道 &dollar;copy&dollar;name 共用 zval 变量容器(内存),然后通过 refcount 来表示当前这个 zval 被多少个变量使用。

看个实例:

<?php$name = 'liugongzi';xdebug_debug_zval('name'); // name: (refcount=1, is_ref=0)string 'liugongzi' (length=9)$copy = $name;xdebug_debug_zval('name'); // name: (refcount=2, is_ref=0)string 'liugongzi' (length=9)// 将新的值赋值给变量 $copy$copy = 'liugongzi handsome';xdebug_debug_zval('name'); // name: (refcount=1, is_ref=0)string 'liugongzi' (length=9)xdebug_debug_zval('copy'); // copy: (refcount=1, is_ref=0)='liugongzi handsome'

注意到没有,当将值 liugongzi handsome 赋值给变量 &dollar;copy 时,name 和 copy 的 refcount 值都变成了 1,在这个过程中发生以下几个操作:

  • 将 &dollar;copy 从 &dollar;name 的 zval(内从)中分离出来(即复制);

  • 将 &dollar;name 的 refcount 减去 1;

  • 对 &dollar;copy 的 zval 进行修改(重新赋值和修改 refcount);

这里只是简单对「写时复制」进行介绍,感兴趣的朋友可以阅读文末给出的参考资料进行更加深入的研究。

  • 查看引用传递变量的 zval 信息

引用传值(&)的「引用计数」规则同普通赋值语句一样,只是 is_ref 标识的值为 1 表示该变量是引用传值类型。

我们现在来看看引用传值的示例:

<?php$age = 'liugongzi';xdebug_debug_zval('age'); // (refcount=1, is_ref=0)string 'liugongzi' (length=9)$copy = &$age;xdebug_debug_zval('age'); // (refcount=2, is_ref=1)string 'liugongzi' (length=9)unset($copy);xdebug_debug_zval('age'); // (refcount=1, is_ref=1)string 'liugongzi' (length=9)
  • 复合类型的引用计数

与标量类型(整型、浮点型、布尔型等)不同,数组(array)和对象(object)这种符合类型的引用计数规则会稍复杂一些。

为了更好的说明,还是先看看数组的引用计数示例:

$a = array( 'meaning' => 'life', 'number' => 42 );xdebug_debug_zval( 'a' );// a:// (refcount=1, is_ref=0)// array (size=2)//  'meaning' => (refcount=1, is_ref=0)string 'life' (length=4)//  'number' => (refcount=1, is_ref=0)int 42

上面的引用计数示意图如下:

PHP垃圾回收及内存管理相关内容有哪些

从图中我们发现复合类型的引用计数规则基本上同标量的计数规则一样,就给出的示例来说,PHP 会创建 3 个 zval 变量容器,一个用于存储数组本身,另外两个用于存储数组中的元素。

添加一个已经存在的元素到数组中时,它的引用计数器 refcount 会增加 1。

$a = array( 'meaning' => 'life', 'number' => 42 );xdebug_debug_zval( 'a' );$a['life'] = $a['meaning'];xdebug_debug_zval( 'a' );// a:// (refcount=1, is_ref=0)// array (size=3)//  'meaning' => (refcount=2, is_ref=0)string 'life' (length=4)//  'number' => (refcount=0, is_ref=0)int 42//  'life' => (refcount=2, is_ref=0)string 'life' (length=4)

大致示意图如下:

PHP垃圾回收及内存管理相关内容有哪些

  • 内存泄露

虽然,复合类型的引用计数规则同标量类型大致相同,但是如果引用的值为变量自身(即循环应用),在处理不当时,就有可能会造成内存泄露的问题。

让我们来看看下面这个对数组进行引用传值的示例:

<?php// @link Http://php.net/manual/zh/function.memory-get-usage.php#96280function convert($size){    $unit=array('b','kb','mb','gb','tb','pb');    return @round($size/pow(1024,($i=floor(log($size,1024)))),2).' '.$unit[$i];}// 注意:有用的地方从这里开始$memory = memory_get_usage();$a = array( 'one' );// 引用自身(循环引用)$a[] =&$a;xdebug_debug_zval( 'a' );var_dump(convert(memory_get_usage() - $memory)); // 296 bunset($a); // 删除变量 $a,由于 $a 中的元素引用了自身(循环引用)最终导致 $a 所使用的内存无法被回收var_dump(convert(memory_get_usage() - $memory)); // 568 b

从内存占用结果上看,虽然我们执行了 unset(&dollar;a) 方法来销毁 &dollar;a 数组,但内存并没有被回收,整个处理过程的示意图如下:

PHP垃圾回收及内存管理相关内容有哪些

可以看到对于这块内存,再也没有符合表(变量)指向了,所以 PHP 无法完成内存回收,官方给出的解释如下:

尽管不再有某个作用域中的任何符号指向这个结构 (就是变量容器),由于数组元素 “1” 仍然指向数组本身,所以这个容器不能被清除 。因为没有另外的符号指向它,用户没有办法清除这个结构,结果就会导致内存泄漏。庆幸的是,php 将在脚本执行结束时清除这个数据结构,但是在 php 清除之前,将耗费不少内存。如果你要实现分析算法,或者要做其他像一个子元素指向它的父元素这样的事情,这种情况就会经常发生。当然,同样的情况也会发生在对象上,实际上对象更有可能出现这种情况,因为对象总是隐式的被引用。 - 摘自 官方文档 Cleanup Problems

简单来说就是「引用计数」算法无法检测并释放循环引用所使用的内存,最终导致内存泄露。

引用计数系统的同步周期回收

由于引用计数算法存在无法回收循环应用导致的内存泄露问题,在 PHP 5.3 之后对内存回收的实现做了优化,通过采用 引用计数系统的同步周期回收 算法实现内存管理。引用计数系统的同步周期回收算法是一个改良版本的引用计数算法,它在引用基础上做出了如下几个方面的增强:

  • 引入了可能根(possible root)的概念:通过引用计数相关学习,我们知道如果一个变量(zval)被引用,要么是被全局符号表中的符号引用(即变量),要么被复杂类型(如数组)的 zval 中的符号(数组的元素)引用,那么这个 zval 变量容器就是「可能根」。

  • 引入根缓冲区(root buffer)的概念:根缓冲区用于存放所有「可能根」,它是固定大小的,默认可存 10000 个可能根,如需修改可以通过修改 PHP 源码文件 Zend/zend_gc.c 中的常量 GC_ROOT_BUFFER_MAX_ENTRIES,再重新编译。

  • 回收周期:当缓冲区满时,对缓冲区中的所有可能根进行垃圾回收处理。

下图(来自 PHP 手册),展示了新的回收算法执行过程:

PHP垃圾回收及内存管理相关内容有哪些

引用计数系统的同步周期回收过程

  1. 缓冲区(紫色框部分,称为疑似垃圾),存储所有可能根(步骤 A);

  2. 采用深度优先算法遍历「根缓冲区」中所有的「可能根(即 zval 遍历容器)」,并对每个 zval 的 refcount 减 1,为了避免遍历时对同一个 zval 多次减 1(因为不同的根可能遍历到同一个 zval)将这个 zvel 标记为「已减」(步骤 B);

  3. 再次采用深度优先遍历算法遍历「可能根 zval」。当 zval 的 refcount 值不为 0 时,对其加 1,否则保持为 0。并请已遍历的 zval 变量容器标记为「已恢复」(即步骤 B 的逆运算)。那些 zval 的 refcount 值为 0 (蓝色框标记)的就是应该被回收的变量(步骤 C);

  4. 删除所有 refcount 为 0 的可能根(步骤 D)。

整个过程为:

采用深度优先算法执行:默认删除 > 模拟恢复 > 执行删除 达到内存回收的目的。

优化后的引用计数算法优势

  • 将内存泄露控制在阀值内,这个由缓存区实现,达到缓冲区大小执行新一轮垃圾回收;

  • 提升了垃圾回收性能,不是每次 refcount 减 1 都执行回收处理,而是等到根缓冲区满时才开始执行垃圾回收。

你可以从 PHP 手册 的回收周期 了解更多,也可以阅读文末给出的参考资料。

PHP 7 的内存管理

PHP 5 中 zval 实现上的主要问题:

  • zval 总是单独 从堆中分配内存;

  • zval 总是存储引用计数和循环回收 的信息,即使是整型(bool / null)这种可能并不需要此类信息的数据;

  • 在使用对象或者资源时,直接引用会导致两次计数;

  • 某些间接访问需要一个更好的处理方式。比如现在访问存储在变量中的对象间接使用了四个指针(指针链的长度为四);

  • 直接计数也就意味着数值只能在 zval 之间共享。如果想在 zval 和 hashtable key 之间共享一个字符串就不行(除非 hashtable key 也是 zval)。

PHP 7 中的 zval 数据结构实现的调整:

最基础的变化就是 zval 需要的内存 不再是单独从堆上分配,不再由 zval 存储引用计数。
复杂数据类型(比如字符串、数组和对象)的引用计数由其自身来存储。 

这种实现的优势:

  • 简单数据类型不需要单独分配内存,也不需要计数;

  • 不会再有两次计数的情况。在对象中,只有对象自身存储的计数是有效的;

  • 由于现在计数由数值自身存储(PHP 有 zval 变量容器存储),所以也就可以和非 zval 结构的数据共享,比如 zval 和 hashtable key 之间;

  • 间接访问需要的指针数减少了。

感谢各位的阅读,以上就是“PHP垃圾回收及内存管理相关内容有哪些”的内容了,经过本文的学习后,相信大家对PHP垃圾回收及内存管理相关内容有哪些这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是编程网,小编将为大家推送更多相关知识点的文章,欢迎关注!

--结束END--

本文标题: PHP垃圾回收及内存管理相关内容有哪些

本文链接: https://www.lsjlt.com/news/300520.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • PHP垃圾回收及内存管理相关内容有哪些
    这篇文章主要讲解了“PHP垃圾回收及内存管理相关内容有哪些”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“PHP垃圾回收及内存管理相关内容有哪些”吧!PHP 垃圾回收与内存管理指引本文将要讲述...
    99+
    2023-06-21
  • PHP 垃圾回收机制与内存管理
    php 的内存管理使用垃圾回收机制,它会在运行时自动释放不再使用的内存,基于引用计数原理运作。为了避免内存泄漏,遵循以下最佳实践:销毁不再需要的变量、避免循环引用、使用弱引用。 PHP...
    99+
    2024-05-03
    php 垃圾回收
  • 分析Java内存管理与垃圾回收
    这篇文章主要介绍“分析Java内存管理与垃圾回收”,在日常操作中,相信很多人在分析Java内存管理与垃圾回收问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”分析Java内存管理...
    99+
    2024-04-02
  • C++ 内存管理中的自动垃圾回收
    c++++ 中自动垃圾回收需要使用第三方工具或库。可以使用智能指针或垃圾回收器库。智能指针自动释放底层对象,而垃圾回收器库使用算法跟踪不再使用的数据结构。案例:使用智能指针 std::s...
    99+
    2024-05-04
    c++ 垃圾回收 垃圾回收器 标准库
  • Python的内存管理和垃圾回收机制
    本篇内容介绍了“Python的内存管理和垃圾回收机制”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!对象的内存使用赋值语句是语言最常见的功能了...
    99+
    2023-06-02
  • JVM教程之内存管理和垃圾回收(三)
    JVM内存组成结构JVM栈由堆、栈、本地方法栈、方法区等部分组成,结构图如下所示:1)堆所有通过new创建的对象的内存都在堆中分配,其大小可以通过-Xmx和-Xms来控制。堆被划分为新生代和旧生代,新生代又被进一步划分为Eden和Survi...
    99+
    2023-05-31
    jvm 内存管理 垃圾回收
  • 详解php内存管理机制与垃圾回收机制
    目录一、内存管理机制二、垃圾回收机制一、内存管理机制 先看一段代码: <?php //内存管理机制 var_dump(memory_get_usage());//获...
    99+
    2024-04-02
  • V8的内存管理与垃圾回收算法是什么
    今天小编给大家分享一下V8的内存管理与垃圾回收算法是什么的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来...
    99+
    2024-04-02
  • php内存管理机制与垃圾回收机制的示例分析
    这篇文章给大家分享的是有关php内存管理机制与垃圾回收机制的示例分析的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。一、内存管理机制先看一段代码:<php//内存管理机制var_dump(memory_get...
    99+
    2023-06-15
  • 如何利用Go语言的垃圾回收器管理内存
    Go语言的垃圾回收器是自动管理内存的,开发者不需要手动释放内存。下面是一些使用Go语言垃圾回收器的最佳实践:1. 避免手动分配内存:...
    99+
    2023-10-08
    Golang
  • Go语言内存管理和垃圾回收的最佳实践
    Go语言在内存管理和垃圾回收方面有一些最佳实践,可以帮助开发人员有效地管理内存和减少垃圾回收的影响。以下是一些常见的最佳实践:1. ...
    99+
    2023-10-08
    Golang
  • 深入剖析Go语言中的垃圾回收与内存管理
    在Go语言中,垃圾回收(Garbage Collection,简称GC)是自动进行的,开发者不需要手动管理内存。Go语言的GC使用的...
    99+
    2023-10-12
    Go语言
  • C++ 递归的内存管理和垃圾回收:优化策略探索
    递归中内存管理面临内存泄漏和过度分配风险,可通过以下策略优化:尾递归优化:避免创建新的堆栈帧,节省内存。动态规划:存储重复计算结果,减少递归调用次数。显式内存管理:手动控制内存分配和释放...
    99+
    2024-05-03
    c++ 递归 垃圾回收
  • 探索Go语言的内存管理特点和垃圾回收机制
    探索Go语言的垃圾回收机制与内存管理特点 引言:随着互联网的发展,开发者们对于编程语言的要求也越来越高。Go语言作为一种静态类型、编译型语言,自诞生之初就凭借其高效的垃圾回收机制和内存管理特点备受关注。本文旨...
    99+
    2024-01-23
    内存管理 垃圾回收机制 Go语言特点
  • 通过Go语言实现高效的内存管理和垃圾回收
    Go语言通过内置的垃圾回收器(Garbage Collector)实现高效的内存管理和垃圾回收。下面是一些Go语言内存管理和垃圾回收...
    99+
    2023-10-08
    Golang
  • linux内存管理相关的函数有哪些
    这篇文章主要介绍“linux内存管理相关的函数有哪些”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“linux内存管理相关的函数有哪些”文章能帮助大家解决问题。 ...
    99+
    2023-04-20
    linux
  • 使用Go语言开发,如何合理管理内存和垃圾回收器
    在Go语言中,内存管理和垃圾回收是由Go运行时系统自动处理的,开发者一般不需要显式地管理内存。Go语言的垃圾回收器使用了标记-清除算...
    99+
    2023-10-08
    Golang
  • 理解Go语言内存优化与垃圾回收的关键技术点
    Go语言的内存优化与垃圾回收的关键技术点包括以下几个方面:1. 标记-清除算法:Go语言使用了标记-清除(mark and swee...
    99+
    2023-10-08
    Golang
  • JVM调优之垃圾定位、垃圾回收算法、垃圾处理器的区别有哪些
    本篇内容主要讲解“JVM调优之垃圾定位、垃圾回收算法、垃圾处理器的区别有哪些”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“JVM调优之垃圾定位、垃圾回收算法、垃...
    99+
    2024-04-02
  • 探索Go语言中的内存优化技术与垃圾回收器管理
    Go语言中的内存优化技术和垃圾回收器管理是为了提高程序性能和减少内存占用。1. 栈分配:Go语言使用栈进行变量的分配,栈上的内存分配...
    99+
    2023-10-08
    Golang
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作