При работе с внешними, по отношению к системе, ресурсами (например, сокеты) освобождать эти ресурсы приходится вручную. Однако, даже при работе с памятью могут существовать ситуации, когда потребуется самостоятельно очищать некоторые ссылки, чтобы избежать утечек памяти. Например, рассмотрим задачи, которые требуют присоединения произвольных атрибутов к произвольным объектам без добавления переменных объекта.
Самый распространённый пример такой задачи - хранение подписчиков на события объектов. Первоначально в ST использовалась очень простая схема, когда для добавления подписчиков используется сообщение #addDependent: с аргументом - подписчиком на события. Сейчас схема усложнилась - появилась возможность подписываться не на все события подряд, а только на те, которые интересуют, но принципиально ничего не поменялось - есть объект публикующий события и объекты-подписчики обрабатывающие события. Первое, что приходит в голову - завести переменную для хранения коллекции объектов-подписчиков. С этой схемой особых проблем нет. Коллекция объектов умрёт вместе с объектом публикующим события, а значит объекты-подписчики не будут удерживаться дольше чем нужно. Именно так и устроен класс Model. Однако, в ST подписаться на некие события можно не только у модели, а у любого объекта (простейший пример - WeakArray уведомляет своих подписчиков о смерти хранимых в нём объектов). Тут можно либо попробовать заводить переменную объекта для подписчиков в каждом классе, который может публиковать события, либо разрешить проблему сразу для всей системы задействовав класс Object. Однако, заводить переменную объекта для хранения подписчиков в классе Object не только нецелесообразно, но и невозможно в текущих реализациях виртуальных машин (ВМ).
Раз нельзя завести переменную, то прийдётся идти другим путём. А именно, заведём глобальный словарь где ключом будет объект публикующий события, а связанным с ключом значением будет коллекция подписчиков. Такие словари, где ключ это некий объект, а значение это присоединённое к объекту свойство, зачастую называют реестрами. Решение это вроде бы универсальное, но реестр то глобальный.
Для борьбы с такими ситуациями и придумали слабые ссылки (weak references). Попробуем задействовать слабые ссылки для реализации нашего реестра. Что же получится, если для хранения ключей словаря использовать слабые ссылки?
Попытка использовать для слабые ссылки для хранения значений еще более нелепа.
Получается, что нужен некий симбиоз двух подходов.
Эфемероны это разновидность ассоциации, то есть пары ключ-значение, и слабой ссылки. Поведение эфемерона такое: если есть сильные ссылки и на сам эфемерон и есть ссылки на ключ, ведущие не из самого эфемерона, то значение эфемерона удерживается. Если есть сильные ссылки на эфемерон, но нет сильных ссылок на ключ, исключая ссылки, ведущие из самого эфемерона (например от "значения" на "ключ"), то эфемерон уведомляется об этом и может обработать эту ситуацию, например, просто освободить значение или выполнить его финализацию. Обратите внимание, что эфемерон уведомляется только в том случае, если на него самого есть сильные ссылки.
Эфемероны были придуманы Георгом Босвортом (George Bosworth) из Digitalk для Visual Smalltalk Enterpraise и описаны Барри Хэесом (см. "Ephemerons: a new finalization mechanism; Barry Hayes; Proceedings of the 1997 ACM SIGPLAN conference on Object-oriented programming systems, languages and applications, 1997, Pages 176 - 183"). В VW эфемероны реализованы Барри Хэесом и Элиотом Мирэндой.
Помимо VW существует реализация эфемеронов для Squeak (упрощенный вариант, когда все ссылки просто об-nil-яются при исчезновении ссылок на ключ); эфемероны есть в GNU Smalltalk начиная с версии 2; эфемероны используются в XEmacs; очень похожий механизм, называемый "key/value weak pointers", существует в Haskell.
Для начала напомню, что в VW могут существовать классы, имеющие различную природу. Тип класса задаётся при его создании параметром indexedType:. Допустимые значения параметра: #none, #objects, #bytes, #immediate, #weak и #ephemeron. По-умолчанию тип проставляется в #none, и, поскольку прикладным программистам нет нужды создавать классы прочих типов, то описание значения этих типов будет дано другим разом. Сейчас же остановимся на типе #ephemeron.
Класс, создаваемый с indexedType: выставленным в #ephemeron, должен содержать минимум одну переменную экземпляра. Допустимо, что это переменная унаследованная от какого либо родительского класса. Такой класс должен быть унаследован от класса с типом #none и подклассы должны быть только типа #ephemeron.
Самая первая переменная экземпляра по-особому обрабатывается сборщиком мусора. Именно она и считается ключем из описания выше. То есть, если на объект в первой переменной экземпляра нет ссылок, то все прочие переменные эфемерона сборщик мусора не трассирует. Это позволяет обнаружить изолированные графы объектов. То есть графы объектов, в которых корнями являются только эфемероны. После нахождения всех таких эфемеронов сборщик мусора ложит их все в очередь для финализации, а затем трассирует все поля эфемеронов, что бы объекты в эфемеронах оставались живы.
До введения эфемеронов финализация в VW была основана на WeakArray. После очистки хоть одной ячейки в WeakArray этот массив получал уведомление и имел возможность обнаружить индексы удалённых объектов. Так как уведомление отсылалось после очистки ссылки, то есть после фактического уничтожения объектов сборщиком мусора, то данные для финализаци, такие как, файловые дескрипторы, нужно было сохранить отдельно. Подобная схема была введена, чтобы исключить "оживление" объекта во время финализации.
Эфемероны в VW поддерживают пообъектную финализацию. После того, как сборщик мусора находит живые эфемероны у которых на ключ (первую по порядку переменную) нет больше ссылок, этим эфемеронам посылается сообщение #mourn. Если на эфемерон нет ссылок, то сообщение #mourn ему не посылается. В VW уже существуют стандартные реализации классов с "indexedType: #ephemeron", которые содержат ряд методов управляющих финализацией. Это WeakKeyAssociation и его потомок класс Ephemeron.
Объекты класса WeakKeyAssociation после выполнения метода #mourn начинают возвращать false на сообщение #isActiveEphemeron. Если такому объекту послать сообщение #isActiveEphemeron: true, то процедура финализации может быть запущена повторно. Метод #mourn в классе WeakKeyAssociation просто проставляет nil в полях "ключ" и "значение".
Поведение потомка WeakKeyAssociation, класса Ephemeron, более сложное. Оно позволяет, кроме выполнения действий по очистке полей эфемерона, освободить так же и сам эфемерон. Одна из переменных экземпляра в классе Ephemeron - manager. По умолчанию, метод #mourn просто посылает менеджеру сообщение #mournKeyOf: self. Класс EphemeronDictionary это словарь, в котором в качестве ассоциаций используются эфемероны. При создании ассоциации в EphemeronDictionary, словарь создаёт эфемерон в котором менеджер это сам словарь. Таким образом, словарь может получить уведомление о том, что его эфемерон готов "умереть" через сообщение #mournKeyOf:. Сам словарь так же позволяет изменить менеджер у всех своих эфемеронов через сообщение #manager:. По-умолчанию, словарь просто удаляет ссылку на "умерший" эфемерон, позволяя сборщику мусора уничтожить граф объектов с эфемероном во главе. И так, чтобы обработать уведомление о том, что на ключ эфемерона больше нет ссылок есть несколько вариантов:
Ярлыки: vw