Решение проблемы использует мощь объектной технологии и, в частности, наследование и переопределение. (Эта техника изучается в последующих лекциях, но ее применение здесь достаточно просто и понятно без детального ознакомления.) Класс MEMORY включает процедуру dispose, в теле которой никакие действия не выполняются:
dispose is
- Действия, которые следует выполнить в случае утилизации;
- по умолчанию действия отсутствуют.
- Вызывается автоматически сборщиком мусора.
do
end
Тогда любой класс, требующий специальных действий всякий раз, когда сборщик утилизирует один из его экземпляров, должен переопределить процедуру dispose так, чтобы она выполняла эти действия. Например, представим, что класс FILE имеет логический атрибут opened и процедуру close. Он может переопределить dispose следующим образом:
dispose is
- Действия, которые следует выполнить в случае утилизации:
- закрыть связанный файл, если он открыт.
- Вызывается автоматически сборщиком мусора.
do
if opened then
close
end
end
Комментарии описывают используемое правило: при утилизации объекта вызывается dispose - либо изначально пустую процедуру (что далеко не самый общий случай), либо версию, переопределенную в классе, представляющего потомка MEMORY.
Сборка мусора и внешние вызовы
Хорошо спроектированная ОО-среда со сборкой мусора должна решать еще одну практическую проблему. Во многих случаях ОО-программы взаимодействуют с программами, написанными на не ОО-языках. В следующих лекциях будет рассмотрено, как лучше обеспечить такое взаимодействие. (См. "Взаимодействие с не ОО-программой", лекцию 13)
Если ПО включает вызовы подпрограмм, написанных на других языках (называемых далее внешними программами), возможно, этим подпрограммам необходимо будет передавать ссылки на объекты. Это потенциально опасно для управления памятью. Предположим, что внешняя подпрограмма имеет следующий вид (преобразованная в соответствии с синтаксисом языка внешней программы):
r (x: SOME_TYPE) is
do
...
a := x
...
end
где a сущность, сохраняющая значение между последовательными вызовами r. Например, а может быть глобальной или статической переменной в традиционных языках, или атрибутом класса в нашей ОО-нотации. Рассмотрим вызов r(y), где y связан с некоторым объектом О1. Возможно, что через некоторое время после вызова, О1 становится недостижимым в объектной части нашей программы, но ссылка на него (от сущности a) остается во внешней программе. Сборщик мусора может - и в конце концов должен - утилизировать О1, но в данном случае это неправильно.
Для таких ситуаций необходимы процедуры, вызываемые из внешней программы, которые защитят нужный объект от сборщика. Эти процедуры могут быть названы:
adopt (a) - усыновлять
wean (a) - отнимать от груди, отлучать
и должны быть частью интерфейса любой библиотеки, обеспечивающей взаимодействие ОО-программ и внешних программ. В следующем разделе описан подобный механизм для языка С. "Усыновление" объекта забирает его из области действия механизма утилизации; "отлучение" - возвращает возможность утилизации.
Передача объектов в не ОО-языки и удерживание ссылки на них внешней программой - дело рискованное. Но избежать этого возможно не всегда. Например, ОО-проект нуждается в специальном интерфейсе между ОО-языком и имеющейся системой управления БД. В этом случае, можно разрешить внешней программе сохранять информацию об объектах. Такие низкоуровневые манипуляции никогда не должны появляться в нормальном программном продукте. Они должны содержаться в обслуживающем классе, написанном с единственной целью - скрыть детали от остальной части программы и защитить ее от возможных неприятностей.
Среда с управлением памятью
В заключение рассмотрим, не вдаваясь в детали, как одна специфическая среда, представленная более широко в последней лекции этой книги, управляет памятью. Это даст пример практического, реально достижимого подхода к проблеме.
Основы
Управление памятью - автоматическое. Среда включает сборку мусора, существующую по умолчанию. Вполне естественен вопрос пользователя "как включить сборщик мусора?". Ответ - он уже включен! В обычном использовании, в том числе и в интерактивных приложениях, он незаметен. Его можно отключить с помощью collection_off.
В отличие от сборщиков в других средах, сборщик мусора не просто освобождает память для повторного использования объектами того же приложения, а возвращает память операционной системе для ее использования другими приложениями (по крайней мере, операционными системами, поддерживающими механизм освобождения памяти навсегда). Ранее показано, как важно это свойство, особенно для систем, работающих долгое время или постоянно.
Дополнительные инженерные цели, возложенные на сборщика мусора при его проектировании: эффективная сборка памяти, небольшие накладные расходы, стартстопный режим работы, позволяющий предотвратить блокировку приложения в критические моменты его работы.
Сложные проблемы
Сборщик мусора сталкивается со следующими проблемами, вызванными практическими ограничениями на размещение объектов в современной ОО-среде:
[x]. ОО-подпрограммы могут вызывать внешние программы, в частности, С-функции, которые могут, в свою очередь, размещать нечто в памяти. Поэтому нужно рассматривать два различных вида памяти: память для объектов и внешнюю память.
[x]. Объекты создаются по-разному. Массивы и строки имеют переменный размер; экземпляры других классов имеют фиксированный размер.
[x]. Наконец, как отмечалось, недостаточно освобождать память для повторного использования в самом ОО-приложении, - нужно возвращать ее навсегда операционной системе.
По этим причинам размещение объектов в памяти не может полагаться на стандартный системный вызов malloc, который, наряду с другими ограничениями, не возвращает память операционной системе. Вместо этого среда запрашивает у ядра операционной системы участки памяти и распределяет объекты в этих участках с помощью собственных механизмов.
Перемещение объектов
Необходимость возвращать память операционной системе порождает одну из самых утонченных частей механизма: сборщик мусора может при необходимости перемещать объекты.
Это свойство вызывает головную боль при реализации сборщика, но оно делает этот механизм устойчивым и практичным. Без него невозможно было бы использовать сборку мусора для долго работающих, критически важных систем.
Внутри ОО-мира нет необходимости задумываться о перемещении объектов, если гарантируется, что система не имеет тенденции постоянного расширения (подразумевается, что общий размер достижимых объектов ограничен). При использовании внешних программ и передачи им объектов эту проблему необходимо рассматривать. Внешняя программа может сохранять ссылки на объекты из ОО-мира в виде простых адресов (указателей в языке С). При попытке использовать эти объекты, находящиеся без защиты, например, в течение 10 минут, возникнут трудности: за это время объект может быть перемещен и по его адресу лежит нечто другое или вообще ничего. Простой библиотечный механизм решает эту проблему: С-функции должны получать сам объект и доступ к нему через специальный макрос, который находит объект, где бы он ни находился.