您的位置 首页 知识分享

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

循环引用是 PHP 应用程序中内存泄漏的常见根源。 当对象之间直接或间接相互引用时,就会产生循环引用。虽然 P…

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

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

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

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

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

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

立即学习“”;

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

理解循环引用

典型的循环引用示例

class A {     public B $b;      public function __construct() {         $this->b = new B($this);     } }  class B {     public function __construct(public A $a) {} }
登录后复制

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

为了检测循环引用,我们可以手动触发垃圾收集器 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> $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 闭包和生成器可以保存循环引用的详细内容,更多请关注php中文网其它相关文章!

本文来自网络,不代表甲倪知识立场,转载请注明出处:http://www.spjiani.cn/wp/8194.html

作者: nijia

发表评论

您的电子邮箱地址不会被公开。

联系我们

联系我们

0898-88881688

在线咨询: QQ交谈

邮箱: email@wangzhan.com

工作时间:周一至周五,9:00-17:30,节假日休息

关注微信
微信扫一扫关注我们

微信扫一扫关注我们

关注微博
返回顶部