Поиск

Детерминированное завершение

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

Заметьте: никаких обещаний насчет времени не дается. Обнаружив, что ссылка больше не используется, поток GC не будет предпринимать больше никаких действий, пока не выполнится код завершения объекта. Однако при обработке всегда может случиться переключение контекста процессора. Это значит, что, с точки зрения приложения, до завершения данного этапа может пройти неопределенно долгое время. Как отмечено выше, очень часто приложению могут быть небезразличны своевременность и порядок исполнения кода завершения. Эти ситуации в общем случае связаны с ресурсами, за которые идет интенсивная конкуренция. Ниже приводятся некоторые примеры таких ресурсов, которые объект должен освободить, как только он больше не используется.

  • Память. Память, занятая графом объекта, после освобождения быстро возвращается в пул для последующего использования.
  • Описатели окон. Объем памяти, занимаемой объектом "окно", в GC не отображает реальный расход памяти. Часть памяти занято внутри самой ОС для представления окна. В связи с этим максимум выделяемого суммарного числа описателей окон может определяться иным, нежели объем доступной памяти, фактором.
  • Соединения с базой данных. Одновременные соединения с базой данных обычно лицензируются, поэтому их число может быть ограничено. Важно своевременно возвращать их в пул для повторного использования.
  • Файлы. Поскольку существует единственный экземпляр некоторого файла, а для многих операций нужен монопольный доступ к нему, важно закрывать описатели файла, когда последний не используется.

Сбор мусора с помощью счетчика ссылок

Счетчик ссылок выполняет разумную функцию, зачастую обеспечивая детерминированное завершение. Но в ряде совсем уж редких случаев, одним из примеров которых служат циклы, счетчик ссылок не может выполнять эту функцию. Фактически при прямом подсчете ссылок никогда не собираются объекты, задействованные в циклах. Большинство из нас, набив достаточно шишек, освоили методики, позволяющие бороться с этим вручную. Но такие методики являются нежелательным источником ошибок. К тому же, начав размещать объекты по разным потокам, вы рискуете получить реентерабельность потоков и, таким образом, внести в свою программу изрядную долю недетерминированности. Некоторые будут доказывать, что коль скоро вы передаете ссылку на объект за пределы сферы прямого контроля тесно связанной программы, вы теряете возможность детерминированного завершения, поскольку вы не имеете ни малейшего понятия, когда этот "далекий" код освободит переданную ссылку, и сделает ли он это вообще. Другие считают, что сложным системам, зависимым от порядка завершения сложных графов объектов, изначально присуща хрупкость конструкции и, весьма вероятно, это приведет к существенным проблемам в сопровождении по мере эволюции кода.

Сбор мусора с помощью трассировки

Трассирующий сборщик подает еще более слабую надежду, чем счетчик ссылок. В силу некоторых причин эта система более интенсивно использует отложенные вычисления при исполнении кода завершения. У объектов есть завершители (finalizers) — методы, которые исполняются, когда объект более недоступен программе. Плюс трассировки в том, что циклы для нее не проблема, а еще больший плюс — что присваивание ссылки представляет собой простую операцию перемещения (об этом чуть позже). За это приходится расплачиваться гарантиями того, что код завершения будет исполнен "сразу" по выходу ссылки из употребления. Ну, а какие гарантии тогда вообще даются? Суть в том, что для корректно работающих программ вызываются завершители объектов (сбойные программы терпят крах или переводят завершитель в бесконечный цикл). Онлайновая документация в этом вопросе проявляет тенденцию к чрезмерной осторожности. Но если у объекта есть метод Finalize, система его вызовет. Проблемы детерминированного завершения это не решает, но важно понимать, что при этом все же производится сбор ресурсов и завершители являются эффективным средством предотвращения утечки ресурсов в программе.