Могу ли я запустить автоматическую сборку мусора PHP, если у меня есть циклические ссылки?

Кажется, я припоминаю способ настроить __destruct для класса таким образом, чтобы циклические ссылки очищались, как только внешний объект выходит из области видимости. Однако простой тест, который я построил, похоже, указывает на то, что он ведет себя не так, как я ожидал/надеялся.

Есть ли способ настроить мои классы таким образом, чтобы PHP правильно их очищал, когда самый внешний объект выходит за рамки?

Я не ищу альтернативные способы написания этого кода, я ищу, можно ли это сделать, и если да, то как? Обычно я стараюсь избегать циклических ссылок такого типа, где это возможно.

class Bar {
    private $foo;
    public function __construct($foo) {
        $this->foo = $foo;
    }
    public function __destruct() {
        print "[destroying bar]\n";
        unset($this->foo);
    }
}

class Foo {
    private $bar;
    public function __construct() {
        $this->bar = new Bar($this);
    }
    public function __destruct() {
        print "[destroying foo]\n";
        unset($this->bar);
    }
}

function testGarbageCollection() {
    $foo = new Foo();
}

for ( $i = 0; $i < 25; $i++ ) {
    echo memory_get_usage() . "\n";
    testGarbageCollection();
}

Вывод выглядит следующим образом:

60440
61504
62036
62564
63092
63620
 [ destroying foo ]
 [ destroying bar ]
 [ destroying foo ]
 [ destroying bar ]
 [ destroying foo ]
 [ destroying bar ]
 [ destroying foo ]
 [ destroying bar ]
 [ destroying foo ]
 [ destroying bar ]

На что я надеялся:

60440
 [ destorying foo ]
 [ destorying bar ]
60440
 [ destorying foo ]
 [ destorying bar ]
60440
 [ destorying foo ]
 [ destorying bar ]
60440
 [ destorying foo ]
 [ destorying bar ]
60440
 [ destorying foo ]
 [ destorying bar ]
60440
 [ destorying foo ]
 [ destorying bar ]

ОБНОВЛЕНИЕ:

Есть несколько отличных ответов на этот вопрос, касающихся PHP> 5.3, но я выбрал ответ, который будет работать с PHP ‹ 5.3, поскольку он действительно относится к моему проекту (PHP 5.2.x).


person Beau Simensen    schedule 23.03.2010    source источник


Ответы (4)


Поскольку __destruct вызывается только после восстановления объекта, вы не можете использовать его для этого. Однако вы можете создать функцию ручной очистки:

class Foo {
  private $bar;
  public function __construct() {
    $this->bar = new Bar($this);
  }
  public function cleanup() {
    $this->bar = null;
  }
  public function __destruct() {
    print "[destroying foo]\n";
  }
}

class Bar {
  private $foo;
  public function __construct($foo) {
    $this->foo = $foo;
  }
  public function __destruct() {
    print "[destroying bar]\n";
  }
}

function testGarbageCollection() {
  $foo = new Foo();
  $foo->cleanup();
}

Я не уверен, насколько это полезно, но это действительно ваш единственный вариант ‹ 5.3

person troelskn    schedule 23.03.2010

Решение может состоять в том, чтобы с PHP >= 5.3 использовать то, что объяснено в Мусор. Коллекция раздела руководства.

В частности, могут представлять интерес функции gc_* -- см. gc_collect_cycles среди прочего.


В случае части кода, которую вы разместили, с PHP >= 5.3 :

  • сборка мусора должна работать
  • НО он будет запущен только тогда, когда PHP сочтет это необходимым!

Второй момент очень важен: поскольку ваш код короткий, он не требует много памяти; что означает, что сборка мусора не будет запускаться в конце каждой итерации цикла:

  • Хорошо, это освободит немного памяти
  • Но это и не нужно, так как памяти все равно много осталось

А поскольку сборка мусора требует времени, PHP не запускает ее слишком часто.


Некоторое время назад я написал об этом статью в своем блоге, где провел несколько тестов; это на французском языке, но график (здесь нет языкового барьера) в в этом разделе ясно показано, что сборщик мусора запускается время от времени, когда это необходимо.

person Pascal MARTIN    schedule 23.03.2010

http://docs.php.net/features.gc.collecting-cycles:

When the garbage collector is turned on, the cycle-finding algorithm as described above is executed whenever the root buffer runs full. The root buffer has a fixed size of 10,000 possible roots (although you can alter this by changing the GC_ROOT_BUFFER_MAX_ENTRIES constant in Zend/zend_gc.c in the PHP source code, and re-compiling PHP). When the garbage collector is turned off, the cycle-finding algorithm will never run. However, possible roots will always be recorded in the root buffer, no matter whether the garbage collection mechanism has been activated with this configuration setting.

http://docs.php.net/features.gc.performance-considerations:

First of all, the whole reason for implementing the garbage collection mechanism is to reduce memory usage by cleaning up circular-referenced variables as soon as the prerequisites are fulfilled. In PHP's implementation, this happens as soon as the root-buffer is full, or when the function gc_collect_cycles() is called.
person VolkerK    schedule 23.03.2010
comment
Обратите внимание, что переполнение корневого буфера является единственным автоматическим запуском алгоритма поиска цикла. Например, он не запустится, когда предел памяти вот-вот достигнет предела. В результате ваш скрипт все еще может прерваться при достижении предела памяти только потому, что PHP слишком тупой, чтобы собирать циклы в этом случае! - person Mikko Rantalainen; 08.02.2013

Начиная с версии 5.3 вы можете

person Your Common Sense    schedule 23.03.2010