<?php
include_once $_SERVER['DOCUMENT_ROOT'] . '/include/shared-manual.inc';
$TOC = array();
$TOC_DEPRECATED = array();
$PARENTS = array();
include_once dirname(__FILE__) ."/toc/features.gc.inc";
$setup = array (
  'home' => 
  array (
    0 => 'index.php',
    1 => 'PHP Manual',
  ),
  'head' => 
  array (
    0 => 'UTF-8',
    1 => 'zh',
  ),
  'this' => 
  array (
    0 => 'features.gc.collecting-cycles.php',
    1 => '回收循环',
    2 => '回收循环',
  ),
  'up' => 
  array (
    0 => 'features.gc.php',
    1 => '垃圾回收',
  ),
  'prev' => 
  array (
    0 => 'features.gc.refcounting-basics.php',
    1 => '引用计数基础',
  ),
  'next' => 
  array (
    0 => 'features.gc.performance-considerations.php',
    1 => '性能方面考虑的因素',
  ),
  'alternatives' => 
  array (
  ),
  'source' => 
  array (
    'lang' => 'zh',
    'path' => 'features/gc.xml',
  ),
  'history' => 
  array (
  ),
);
$setup["toc"] = $TOC;
$setup["toc_deprecated"] = $TOC_DEPRECATED;
$setup["parents"] = $PARENTS;
manual_setup($setup);

contributors($setup);

?>
<div id="features.gc.collecting-cycles" class="sect1">
   <h2 class="title">回收循环</h2>
   <p class="para">
    传统上，像 PHP 之前使用的引用计数内存机制无法解决循环引用内存泄漏的问题；然而，从 5.3.0 版本开始，PHP 实施了<a href="https://pages.cs.wisc.edu/~cymen/misc/interests/Bacon01Concurrent.pdf" class="link external">&raquo;&nbsp;引用计数系统中的同步循环回收</a>论文中的同步算法来解决这个问题。
   </p>
   <p class="para">
    对算法的完全说明有点超出这部分内容的范围，将只介绍其中基础部分。首先，需要确立一些基本规则。如果 refcount 增加，则该变量仍在使用中，因此不是垃圾。如果 refcount
    减少到 0，则 zval 可以释放。这意味着只有当引用计数参数减少到非零值时，才能创建垃圾循环。其次，在垃圾循环中，可以通过检查是否可以将 refcount 减少 1，并检查哪些
    zval 的 refcount 为 0 来确定哪些部分是垃圾。
   </p>
   <p class="para">
     <div class="mediaobject">
      
      <div class="imageobject">
       <img src="images/12f37b1c6963c1c5c18f30495416a197-gc-algorithm.png" alt="垃圾回收算法" width="614" height="814" />
      </div>
     </div>
   </p>
   <p class="para">
    为避免不得不检查所有引用计数可能减少的垃圾循环，这个算法把所有可能根(possible roots 都是zval变量容器),放在根缓冲区(root buffer)中(用紫色来标记，称为疑似垃圾)，这样可以同时确保每个可能的垃圾根(possible garbage root)在缓冲区中只出现一次。仅仅在根缓冲区满了时，才对缓冲区内部所有不同的变量容器执行垃圾回收操作。看上图的步骤 A。
   </p>
   <p class="para">
    在步骤 B 中，模拟删除每个紫色变量。模拟删除时可能将不是紫色的普通变量引用数减&quot;1&quot;，如果某个普通变量引用计数变成0了，就对这个普通变量再做一次模拟删除。每个变量只能被模拟删除一次，模拟删除后标记为灰，确保不会对同一个变量容器减两次&quot;1&quot;。
   </p>
   <p class="para">
    在步骤 C 中，模拟恢复每个紫色变量。恢复是有条件的，当变量的引用计数大于0时才对其做模拟恢复。同样每个变量只能恢复一次，恢复后标记为黑，基本就是步骤 B 的逆运算。这样剩下的一堆没能恢复的就是该删除的蓝色节点了，在步骤 D 中遍历出来真的删除掉。
   </p>
   <p class="para">
    算法中都是模拟删除、模拟恢复、真的删除，都使用简单的遍历即可（最典型的深搜遍历）。复杂度为执行模拟操作的节点数正相关，不只是紫色的那些疑似垃圾变量。
   </p>
   <p class="para">
    对算法的工作原理有了基本的了解后，现在可以回顾一下如何与 PHP 集成。默认情况下，PHP 的垃圾回收器是打开的。然而，有个 <var class="filename">php.ini</var>
    设置可以进行更改：<a href="info.configuration.php#ini.zend.enable-gc" class="link">zend.enable_gc</a>。
   </p>
   <p class="para">
    当打开垃圾回收器时，如上所述的循环查找算法将在根缓冲区满时执行。根缓冲区的大小是固定的，可以容纳 10,000 个可能的根（尽管可以通过更改
    PHP 源代码中的 <code class="literal">Zend/zend_gc.c</code> 中的 <strong><code>GC_THRESHOLD_DEFAULT</code></strong> 常量并重新编译 PHP
    来修改这个值）。当关闭垃圾回收器时，循环查找算法将永不运行。然而，无论是否使用此配置激活垃圾回收机制，可能根都将始终记录在根缓冲区中。
   </p>
   <p class="para">
    如果在垃圾回收机制关闭时，根缓冲区存满了可能的根，那么将不会记录进一步的可能根。算法永远不会分析那些没有记录的可能根。如果他们是循环引用的一部分，将永不会清除从而导致内存泄漏的产生。
   </p>
   <p class="para">
    即使在垃圾回收机制不可用时，可能根也被记录的原因是，相对于每次找到可能根后检查垃圾回收机制是否打开而言，记录可能根的操作更快。不过垃圾回收和分析机制本身要耗不少时间。
   </p>
   <p class="para">    
    除了改变配置中的 <a href="info.configuration.php#ini.zend.enable-gc" class="link">zend.enable_gc</a> 之外，还可以通过调用 <span class="function"><a href="function.gc-enable.php" class="function">gc_enable()</a></span>
    或 <span class="function"><a href="function.gc-disable.php" class="function">gc_disable()</a></span> 来启用/禁用垃圾回收机制。调用这些函数与通过配置打开或关闭机制的效果相同。即使可能的根缓冲区尚未满，还可以强制回收循环。为此，可以使用
    <span class="function"><a href="function.gc-collect-cycles.php" class="function">gc_collect_cycles()</a></span> 函数。该函数将返回算法回收的循环数量。
   </p>
   <p class="para">
    允许打开和关闭垃圾回收机制并且允许自主的初始化的原因，是由于你的应用程序的某部分可能是高时效性的。在这种情况下，你可能不想使用垃圾回收机制。当然，对你的应用程序的某部分关闭垃圾回收机制，是在冒着可能内存泄漏的风险，因为一些可能根也许存不进有限的根缓冲区。因此，就在你调用<span class="function"><a href="function.gc-disable.php" class="function">gc_disable()</a></span>函数释放内存之前，先调用<span class="function"><a href="function.gc-collect-cycles.php" class="function">gc_collect_cycles()</a></span>函数可能比较明智。因为这将清除已存放在根缓冲区中的所有可能根，然后在垃圾回收机制被关闭时，可留下空缓冲区以有更多空间存储可能根。
   </p>
  </div><?php manual_footer($setup); ?>