PHP 闭包和生成器可以保存循环引用

php 闭包和生成器可以保存循环引用

循环引用是 PHP 应用程序中内存泄漏的常见根源。 当对象之间直接或间接相互引用时,就会产生循环引用。虽然 PHP 的垃圾收集器能够识别并清除这些循环引用,但这会消耗 CPU 资源,并可能导致应用程序性能下降。

当内存中存在 10,000 个潜在的循环对象或数组,且其中一个超出作用域时,垃圾收集器就会被触发。

如果少量对象占用大量内存,垃圾收集器可能永远不会被触发。即使内存被孤立对象占用,也可能达到内存限制。

因此,识别并避免循环引用至关重要。 对于 Web 应用,理想情况下,应该禁用垃圾收集器,让 PHP 在发送响应后释放所有内存。 但对于长时间运行的脚本(如守护进程或工作线程),这样做存在风险,因为内存泄漏会累积,频繁的垃圾收集会降低应用程序速度。

本文将探讨闭包和生成器如何产生循环引用,以及如何避免这些问题。

立即学习“PHP免费学习笔记(深入)”;

理解循环引用典型的循环引用示例使用弱引用避免循环引用闭包与循环引用生成器与循环引用总结

理解循环引用

典型的循环引用示例

class A {    public B $b;    public function __construct() {        $this->b = new B($this);    }}class B {    public function __construct(public A $a) {}}

在这个例子中,AB 对象相互引用。创建 A 实例时,会创建一个引用 AB 实例,从而形成循环引用。

为了检测循环引用,我们可以手动触发垃圾收集器 gc_collect_cycles(),并使用 gc_status() 查看已收集的引用数量。

// 创建对象但不赋值给变量new A();gc_collect_cycles();print_r(gc_status());

输出结果类似:

array (    ...    [collected] => 2    ...)

这表明垃圾收集器检测并清除了两个存在循环引用的对象。

还可以使用 xdebug_debug_zval() 函数查看对象的引用计数。

使用弱引用避免循环引用

解决循环引用的一种方法是使用弱引用。弱引用是指不会阻止垃圾收集器回收其所引用对象的引用。在 PHP 中,可以使用 WeakReference 类创建弱引用。

需要对代码进行一些修改。B 类现在存储 WeakReference 对象而不是 A 对象。 必须使用 WeakReference 对象的 get() 方法访问 A 对象。

class A {    public B $b;    public function __construct() {        $this->b = new B($this);    }}class B {    /** @var WeakReference $a */    public WeakReference $a;    public function __construct(A $a) {        $this->a = WeakReference::create($a);    }}

再次运行垃圾收集器:

// 创建对象但不赋值给变量new A();gc_collect_cycles();print_r(gc_status());// [collected] => 0

输出结果中,收集的引用数量为 0。

提示 1: 仅在必要时使用弱引用来避免循环引用。

闭包与循环引用

PHP 中的闭包允许创建一个函数,该函数可以访问其父作用域中的变量。如果不注意,这可能会导致循环引用。

function createCircularReference() {    $a = new stdClass();    $a->b = function () use ($a) {        return $a;    };    return $a;}

在这个例子中,闭包 $a->b 引用了父作用域中的变量 $a。由于引用是显式的,所以很容易发现循环引用。

但是,如果使用闭包的简写语法(箭头函数),同样的问题可能会更隐蔽。使用箭头函数时,变量 $a 没有在闭包中显式引用,但仍然通过引用捕获。

function createCircularReference() {    $a = new stdClass();    $a->b = fn() => $a;    return $a;}createCircularReference();gc_collect_cycles();print_r(gc_status());// [collected] => 2

在这个例子中,收集的引用数量为 2,表示存在循环引用。

在闭包中引用 $this

在类方法中创建的任何非静态闭包都会持有对对象实例 ($this) 的引用,即使 $this 没有被访问。

class A {    public Closure $closure;    public function __construct() {        $this->closure = function () {};    }}new A();gc_collect_cycles();print_r(gc_status());// [collected] => 2

这是因为 $this 引用总是被闭包隐式捕获。可以使用 Reflection::getClosureThis() 来访问它。

class A {    public Closure $closure;    public function __construct() {        $this->closure = static function () {};    }}new A();gc_collect_cycles();print_r(gc_status());// [collected] => 0

如果闭包是在全局作用域或静态方法中创建的,则 $this 引用为 null。

提示 2: 如果不需要 $this,创建闭包时始终使用 static function () {}static fn () => {}

生成器与循环引用

生成器在未耗尽之前会保留引用。

在这个例子中,类将生成器存储在一个属性中,但生成器持有对对象实例 $this 的引用。生成器的行为类似于闭包,并保留对对象实例的引用。

class A {    public iterable $iterator;    public function __construct() {        $this->iterator = $this->generator();    }    private function generator(): Generator {        yield;    }}new A();gc_collect_cycles();print_r(gc_status());// [collected] => 1

类实例被垃圾收集器回收,因为它持有对生成器的引用,而生成器又持有对对象实例的引用。

一旦生成器耗尽,引用就会被释放,对象实例也会被删除。

iterator_to_array((new A())->iterator);gc_collect_cycles();print_r(gc_status());// [collected] => 0

提示 3: 确保始终迭代以耗尽生成器。

提示 4: 使用静态方法或闭包创建生成器,避免保留对对象实例的引用。

总结

循环引用是 PHP 中内存泄漏的常见原因。即使垃圾收集器可以检测和清理循环引用,它也会消耗 CPU 资源并降低应用程序速度。必须识别创建这些循环引用并调整代码以防止它们发生。使用弱引用可以避免循环引用,但一些简单的技巧可以帮助你从一开始就避免循环引用:

如果不需要 $this,创建闭包时使用 static function () {}static fn () => {}。确保始终迭代以耗尽生成器。使用静态方法或闭包创建生成器,避免保留对对象实例的引用。

更多阅读

PHP 垃圾收集 — 性能注意事项什么是 PHP 中的垃圾收集以及如何充分利用它?memprof — PHP 内存分析器,用于查找 PHP 脚本中的内存泄漏。Xdebug 的内置分析器

以上就是PHP 闭包和生成器可以保存循环引用的详细内容,更多请关注创想鸟其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/1284146.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2025年12月11日 00:26:04
下一篇 2025年12月11日 00:26:19

相关推荐

  • PHP闭包函数:显式参数传递和隐式变量捕获有何区别?

    PHP闭包函数参数传递机制深度解析 PHP闭包函数(匿名函数)能够访问其定义作用域中的变量,但其参数传递方式存在细微差别,主要分为显式参数传递和隐式变量捕获两种。本文将详细对比这两种方式。 示例代码展示了两种闭包函数的定义: function (int $timer_id) use ($sms, $…

    2025年12月11日
    000
  • Dcat Admin表单多行布局下,Radio单选按钮如何实现联动?

    Dcat Admin表单多行布局下Radio单选按钮联动解决方案 在Dcat Admin框架下构建表单时,实现表单元素联动,例如根据单选按钮(Radio)的选择动态显示或隐藏其他字段,经常会遇到问题,尤其是在多行布局下。本文将解决Dcat Admin多行布局中Radio单选按钮联动失效的问题。 问题…

    2025年12月11日
    000
  • PHP记录:PHP日志分析的最佳实践

    php日志记录对于监视和调试web应用程序以及捕获关键事件,错误和运行时行为至关重要。它为系统性能提供了宝贵的见解,有助于识别问题,并支持更快的故障排除和决策 – 但仅当它有效地实施时。 在此博客中,我概述了PHP记录以及它在Web应用程序中的使用方式。然后,我概述了一些关键的最佳实践,…

    2025年12月11日
    000
  • PHP常量继承中self关键字的行为:为什么子类重定义父类常量后,结果会因PHP版本而异?

    深入解析php常量继承与self关键字:php版本差异引发的思考 本文分析一个关于PHP常量继承和self关键字的有趣现象,解释为什么子类重定义父类常量后,输出结果会因PHP版本而异。 问题源于以下代码片段: class A { const X = 1; const Y = self::X;}cla…

    2025年12月11日
    000
  • Swiper轮播图鼠标悬停停止报错:如何解决swiper is not defined?

    Swiper轮播图鼠标悬停暂停功能实现及“swiper is not defined”错误排查 在Swiper轮播图中,实现鼠标悬停暂停自动播放,离开后继续播放,是一个常见的需求。本文将针对Swiper 3.4.2版本,分析一个常见的“swiper is not defined”错误,并提供解决方案…

    2025年12月11日
    000
  • Swiper自动轮播鼠标悬停停止报错:如何解决“swiper is not defined”问题?

    Swiper轮播图鼠标悬停暂停及继续播放功能实现及“swiper is not defined”错误解决方法 许多开发者在使用Swiper插件实现鼠标悬停暂停自动轮播功能时,可能会遇到swiper is not defined错误。本文将详细分析此问题并提供解决方案。 问题描述: 在Swiper 3…

    2025年12月11日
    000
  • PHP中如何用匿名函数实现闭包并返回指定字符串?

    PHP匿名函数闭包:返回指定字符串 本文演示如何在PHP中利用匿名函数创建闭包,并使其返回特定字符串。 我们将使用一个函数来调用另一个匿名函数,后者负责返回预定义的字符串。 以下代码展示了实现方法: function myClosureExample($prefix, callable $closu…

    2025年12月11日
    100
  • TP5.1自定命令如何调用其他控制器方法?

    ThinkPHP 5.1 自定义命令调用其他控制器方法的解决方法 在ThinkPHP 5.1中,自定义命令和控制器运行在不同的环境下:命令行环境和HTTP请求环境。直接在自定义命令中调用控制器方法会因为作用域差异而失败。 问题描述:尝试在自定义命令中调用同一目录下的其他控制器方法,但执行失败。 解决…

    2025年12月11日
    000
  • PHP反射递归调用中,$reflect变量是如何保持不变的?

    PHP反射递归调用中局部变量$reflect的生存周期 本文分析PHP反射机制中递归调用时$reflect变量的行为。以下代码片段演示了这个微妙的问题: public function get($class){ if (isset($this->objectTree[$class])) { $…

    2025年12月11日
    000
  • PHP静态方法利弊权衡:到底该不该在TP框架中全面使用?

    ThinkPHP框架中全面使用静态方法的利与弊分析 在ThinkPHP框架开发中,有人建议全面采用静态方法以减少对象创建。这种做法是否可行?本文将深入探讨PHP静态方法的优缺点,并分析其在ThinkPHP框架中的适用性。 静态方法的优势: 内存效率高:静态方法无需为每个对象分配内存,降低内存消耗。性…

    2025年12月11日
    000
  • WordPress:限制是由工具还是开发人员放置的?

    资深开发者眼中,WordPress常被误解为功能受限的平台。然而,我的经验恰恰相反。WordPress已发展成为一个强大的生态系统,足以构建复杂的商业应用。Gutenberg编辑器、完善的本地API以及与现代技术(如React)的集成,都展现了其无限潜力。 关键在于,WordPress只是我们工具箱…

    2025年12月11日
    000
  • 检查一个字符串交换是否可以使字符串相等

    判断字符串交换后是否相等 难度: 简单 主题: 哈希表,字符串,计数 给定两个长度相等的字符串 s1 和 s2。一次字符串交换操作是指选择字符串中两个索引(可以相同),并交换这两个索引上的字符。 如果可以通过恰好一次字符串交换操作使两个字符串相等,则返回 true;否则,返回 false。 示例 1…

    2025年12月11日
    000
  • 设计一个数字容器系统

    设计一个高效的数字容器系统,支持以下操作: 插入/替换: 将指定索引处的值替换为新值。如果索引不存在,则插入新值。查找最小索引: 返回给定数字在容器中出现的最小索引。如果数字不存在,则返回 -1。 挑战难度: 中等 相关主题: 哈希表,设计模式,最小堆(优先队列) 示例: [“NumberConta…

    2025年12月11日
    000
  • 扩展Laravel Optimize命令

    Laravel optimize 命令用于提升应用性能。它将配置文件、路由和视图编译成单个文件,减少运行时加载的文件数量,从而提高效率。 建议在部署到生产环境前使用此命令。 执行命令: php artisan optimize optimize 命令底层调用 IlluminateFoundation…

    2025年12月11日
    000
  • 与作曲家制作和共享PHP库

    Composer已成为PHP项目依赖管理和代码复用的核心工具。无论您是贡献开源项目还是提升个人开发效率,学习创建Composer包都是一项非常有价值的技能。本文将引导您完成构建和共享个人PHP库的完整流程。 准备工作 在开始之前,请确保您已具备以下条件: 扎实的PHP和Composer基础知识。已在…

    2025年12月11日
    000
  • 升级到PHP

    本文档记录了在Ubuntu系统上安装或升级PHP 8.2的步骤,希望能帮助到您和其他人。 首先,更新系统软件包列表: sudo dpkg -l | grep php | tee packages.txtsudo add-apt-repository ppa:ondrej/php # 按提示键入sud…

    2025年12月11日
    000
  • 清除数字

    算法题:清除数字 (难度:简单) 题目描述:给定一个字符串 s,其中包含小写英文字母和数字。你需要重复执行以下操作,直到字符串中不再包含数字:找到第一个数字,并删除该数字以及它左侧最近的非数字字符。最终返回删除所有数字后的字符串。 示例: 输入: s = “abc” 输出: …

    2025年12月11日
    000
  • 通过Laravel和Livewire邀请开发ERP

    大家好, 我最近完成了一个基于Web的计费系统项目,使用Laravel和Livewire框架构建。最初,这个项目只是为了满足朋友的需求,帮他创建一个简单的客户交易记录系统。 我通过在数据库中存储产品信息,然后将这些产品添加到发票中来实现发票/账单的创建功能。 随着项目的进展,我逐步添加了更多功能,例…

    2025年12月11日
    000
  • WebFormSPHP更新到WebFARSJS

    php webforms核心技术详解:服务器端与客户端的无缝交互 WebForms核心技术实现了服务器端PHP类与客户端WebFormsJS库的无缝通信。 最新的PHP WebForms类已完全兼容最新版本的WebFormsJS库,并充分利用了1.6版本的所有新功能。 该技术支持所有HTML事件(例…

    2025年12月11日 好文分享
    000
  • Laravel中的数据库迁移

    Laravel数据库迁移:高效管理数据库架构 Laravel凭借其优雅的语法和强大的功能,成为流行的PHP框架。其数据库管理系统尤为出色,包含查询构建器、Eloquent ORM和迁移功能。迁移功能允许开发者精确控制数据库模式,简化团队协作,并在不同环境中轻松维护数据库一致性。本文将深入探讨Lara…

    2025年12月11日
    000

发表回复

登录后才能评论
关注微信