Алгоритм сбора мусора

Как мы знаем, в РНР существует такое понятие, как ссылка на объект. Ссылочная переменная хранит не сам объект, а лишь его адрес в памяти - таким образом, на один и тот же объект могут ссылаться сразу несколько переменных. Забегая вперед скажем, что объекты, на которые в программе не осталось ссылок, РНР немедленно удаляет из памяти (предварительно вызвав деструкторы). Вся специфика заключена в словах "не осталось ссылок" и "немедленно".

ПРИМЕЧАНИЕ

Представьте, что объект - это пальто, сданное в гардероб. Тогда в качестве ссылки будет выступать номерок на это пальто, выдаваемый гардеробщиком. Этот номерок можно "копировать" - например, отдав в мастерскую (аналог присваивания переменных). При этом пальто остается тем же самым и не изменяется. Что произойдет с пальто, если человек уничтожит свой номерок (обнулит ссылку на объект)?.. Наверное, через некоторое время гардеробщик сообразит, что пальто больше не нужно и лишь занимает вешалку, и отправит его на утилизацию - диспетчер динамической памяти (или, как его еще называют, сборщик мусора) удалит объект-пальто. Однако РНР гораздо "шустрее": если гардеробщику требуется некоторое время на принятие решения, то интерпретатор сразу же обнаруживает объекты, на которые нет ссылок, и удаляет их, не задерживаясь.

Сложности начинаются, когда на некоторый объект имеется более одной ссылки. В этом случае, конечно же, уничтожение нужно провести только при обнулении последней ссылки, но ни в коем случае - промежуточных. Но как же определить, ссылается ли кто-то еще на объект или же нет?..

В этом и заключена специфика алгоритма со счетчиком ссылок, применяемого в РНР (а также в Perl), одновременно его сила и слабость. Любой объект, который вы создаете, содержит в себе скрытое поле, хранящее так называемый счетчик ссылок. Каждый раз, когда в программе появляется новая ссылка на объект, этот счетчик увеличивается на 1 (обычно это происходит при выполнении операции присваивания $alias = $source: раньше ссылка хранилась только в $source, а теперь и в $alias, и в $source). Соответственно, при удалении ссылки счетчик уменьшается на 1. Например, операция unset ($alias), $alias - "что угодно", а также выход локальной переменной функции за область видимости приводит к потере ссылки на объект, которая раньше находилась в $alias. Ясно, что при обнулении счетчика на объект больше никто не ссылается, а потому его можно спокойно удалить из памяти, что РНР и делает. Таким образом, объект удаляется после некоторой операции присваивания, приводящей к потере последней ссылки на него.

Удаление объекта или массива - довольно сложная процедура. Интерпретатору необходимо:

· удалить все ссылки, которые содержит сам этот объект (например, при удалении массива нужно обнулить все элементы, которые в нем содержатся - на случай, если они сами являются объектами). Если в процессе этой операции какой-то другой подчиненный объект теряет последнюю ссылку, то он также будет удален, и т. д. -рекурсивно;

· вызвать деструктор; деструкторы играют весьма важную роль в ООП, так что полная их поддержка в алгоритме со счетчиком ссылок - это сильная сторона метода;

· освободить занимаемую память; эта операция выполняется в самый последний момент и может рассматриваться как низкоуровневая.

Циклические ссылки

Алгоритмы сборки мусора с использованием счетчика ссылок, как правило, имеют один очень существенный недостаток. Речь идет о циклических ссылках. Давайте рассмотрим пример (листинг 13).

Листинг 13 Проблемы алгоритма со счетчиком ссылок. Файл refcount.php

В программе создаются два объекта: $father и $child. При этом объект-отец хранит ссылки на всех своих потомков, а каждый сын - ссылку на отца. Это и называется циклическими ссылками: если идти "вдоль них", мы никогда не остановимся. Цикличе­ские ссылки встречаются на практике очень часто, особенно при описании иерархических структур.

Теперь взгляните на предпоследнюю строчку кода. Мы присваиваем ссылочным переменным $father и $child значение NULL, в результате чего счетчик ссылок в соответствующих объектах уменьшается на 1.

А теперь - "сюрприз": несмотря на то, что в программе мы уже никак не сможем "добраться" до данных объектов $father и $child (мы же уничтожили эти ссылки), память для них все же не освобождается, и они остаются "висеть" мертвым грузом, хотя к ним уже и нельзя получить доступ! Убедиться в этом можно, запустив скрипт листинга 13 в браузере:

Пока что все живы... Убиваем всех.

все умерли, конец программы.

Father умер.

Child умер.

Как видите, сообщение "Все умерли", выводящееся в конце программы, оказывается самым первым, а не последним по списку. Это означает, что деструкторы были вызваны уже после завершения работы скрипта.

Давайте теперь в качестве эксперимента уберем строчку: $father->children[] = $child. Таким образом, теперь в программе уже не будет кольцевых ссылок, и результат ее работы станет выглядеть так:

Пока что все живы... Убиваем всех.

Child умер.

Father умер.

Все умерли, конец программы.

Как видите, если циклических ссылок в программе нет, объекты уничтожаются в правильном порядке.


Понравилась статья? Добавь ее в закладку (CTRL+D) и не забудь поделиться с друзьями:  



double arrow
Сейчас читают про: