引用计数(Reference Counting) 无法解决循环引用问题时,PHP 的垃圾回收机制会触发 标记清除算法(Mark-and-Sweep Algorithm)。这是 PHP 5.3 引入的一项改进,用于更有效地管理内存并避免内存泄漏。


1. 引用计数的基本原理

1.1 定义
  • 引用计数 是一种简单的垃圾回收机制,每个对象都有一个引用计数器(refcount),记录有多少地方引用了该对象。

  • 工作方式

    • 创建对象时,引用计数加 1。
    • 复制对象或引用时,引用计数加 1。
    • 销毁对象或超出作用域时,引用计数减 1。
  • 释放内存

    • 当引用计数降为 0 时,对象的内存会被立即释放。
1.2 局限性
  • 循环引用问题

    • 当两个或多个对象互相引用时,即使它们已经不再被外部引用,引用计数也不会降为 0,导致内存泄漏。
  • 示例

    class A {
        public $b;
    }
    
    class B {
        public $a;
    }
    
    $a = new A();
    $b = new B();
    $a->b = $b;
    $b->a = $a;
    
    unset($a, $b); // 即使 unset,$a 和 $b 的引用计数仍为 1
    

2. 标记清除算法的引入

2.1 解决循环引用问题
  • 定义

    • 标记清除算法通过从根节点开始标记所有可达对象,并清理未被标记的对象,解决了循环引用问题。
  • 适用场景

    • 当引用计数无法解决问题时,标记清除算法会介入。
2.2 触发条件
  • 垃圾回收器的阈值

    • PHP 的垃圾回收器会维护一个双向链表(称为“GC Roots”),用于跟踪可能包含循环引用的对象。
    • 当链表中的对象数量达到一定阈值(默认为 10,000 个)时,垃圾回收器会触发标记清除算法。
  • 手动触发

    • 开发者可以通过 gc_collect_cycles() 手动触发标记清除算法。

3. 标记清除算法的具体实现

3.1 标记阶段
  • 定义

    • 从根节点(如全局变量、活动符号表等)出发,递归标记所有可达对象。
  • 实现步骤

    1. 遍历 GC Roots 链表中的所有对象。
    2. 对于每个对象,检查其是否可以通过根节点直接或间接访问。
    3. 如果对象可达,则将其标记为“存活”。
  • 示例

    Root -> Object A -> Object B
                 ↘
                  -> Object C
    
    • 假设 Object A 是根节点,则 Object BObject C 会被标记为可达。
3.2 清除阶段
  • 定义

    • 遍历堆内存,查找未被标记的对象,并释放它们占用的内存。
  • 实现步骤

    1. 遍历 GC Roots 链表中的所有对象。
    2. 如果对象未被标记,则将其视为垃圾并释放内存。
    3. 清理标记状态,为下一次垃圾回收做准备。
  • 示例

    Heap:
    Object A (Marked)
    Object B (Marked)
    Object D (Unmarked)
    
    • 在清除阶段,Object D 被释放。

4. PHP 垃圾回收的优化

4.1 分代垃圾回收
  • 定义

    • 尽管 PHP 没有明确的分代垃圾回收机制,但它通过区分短期对象和长期对象来优化性能。
  • 实现方式

    • 短期对象(如局部变量)通常很快变为不可达,优先被清理。
    • 长期对象(如全局变量)会在多次垃圾回收中保留。
4.2 增量式垃圾回收
  • 定义

    • PHP 的垃圾回收器将标记清除过程分成多个小步骤,逐步完成,以减少程序暂停时间。
  • 优点

    • 减少 Stop-the-World 时间,提高程序响应速度。

5. 示例代码分析

以下是一个完整的 PHP 示例,展示引用计数和标记清除算法的工作过程:

class A {
    public $b;
}

class B {
    public $a;
}

$a = new A();
$b = new B();
$a->b = $b; // $a 引用 $b
$b->a = $a; // $b 引用 $a

// 此时,$a 和 $b 的引用计数均为 2
unset($a, $b); // 引用计数降为 1,但仍然存在循环引用

// 手动触发垃圾回收
gc_collect_cycles(); // 标记清除算法介入,清理 $a 和 $b

6. 总结

PHP 的垃圾回收机制结合了 引用计数标记清除算法

  1. 引用计数 用于快速释放不再使用的对象。
  2. 标记清除算法 用于解决循环引用问题,确保内存不会泄漏。

理解这两种机制的工作方式及其关系,有助于开发者编写更高效的代码,并避免常见的内存管理问题(如内存泄漏、性能瓶颈)。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐