Финализа́тор в объектно-ориентированных языках программирования, использующих механизм сборки мусора, — специальный метод, вызываемый средой исполнения перед удалением объекта сборщиком мусора.
Финализатор — это метод класса, который автоматически вызывается средой исполнения в промежутке времени между моментом, когда объект этого класса опознаётся сборщиком мусора как неиспользуемый, и моментом удаления объекта (освобождения занимаемой им памяти). Финализатор для конкретного объекта всегда выполняется после того, как программа прекращает использовать данный объект и до того, как занимаемая объектом память будет освобождена сборщиком мусора. Удобно считать, что финализатор вызывается непосредственно перед удалением объекта из памяти, хотя это обычно не гарантируется.
Внешне финализатор схож с деструктором класса, однако в действительности эффект действия и область применения этих методов существенно различаются. Различие вызвано тем, что момент вызова финализатора, в отличие от деструктора, не определён жёстко: финализатор всегда вызывается перед уничтожением объекта сборщиком мусора, но момент уничтожения зависит от режима работы сборщика мусора, объёма доступной оперативной памяти и активности использования памяти программой. Так, если свободной памяти мало и создание новых объектов происходит постоянно, потребность в сборке мусора возникает часто и финализатор, соответственно, с высокой вероятностью будет вызван вскоре после прекращения использования объекта. Если же памяти много, а потребление её программой мало, то от прекращения использования объекта до сборки мусора (и вызова финализатора) может пройти длительное время. Более того, если памяти много и новые объекты почти или совсем не создаются, то сборщик мусора может вообще не вызываться, а по завершении программы вся выделенная ей память будет просто возвращена операционной системе; в этом случае финализатор, возможно, вообще не будет вызван.
В то время как деструкторы очень часто используются для освобождения занятых объектом дефицитных системных ресурсов (таких как доступ к файлу или к аппаратному обеспечению), финализаторы, ввиду отмеченных выше особенностей, использовать подобным образом обычно не рекомендуется. Безусловно, финализатор может закрывать файл или сообщать операционной системе, что устройство более не используется, однако от момента прекращения использования объекта до момента вызова финализатора может пройти неопределённое количество времени, и всё это время занятые объектом ресурсы не будут использоваться, но будут оставаться занятыми.[1]
Финализаторы непредсказуемы, зачастую опасны и, чаще всего, не нужны.
— Joshua Bloch. Effective Java. Addison-Westley, 2001.
Вследствие сказанного, применение финализаторов весьма ограниченно. Для освобождения ресурсов в языках со сборкой мусора используется шаблон проектирования «dispose». Язык программирования C# поддерживает шаблон «dispose» неявно через интерфейс IDisposable
и ключевое слово using
, в Java 7 введён аналогичный механизм «try-with-resources».
Один из редких случаев, когда финализатор действительно необходим — реализация классом собственных механизмов работы с памятью, опирающихся на сторонний код, не управляемый системой сборки мусора, например, когда класс Java использует код, написанный на C, чтобы достичь максимальной эффективности или выполнить низкоуровневые операции. Для работы внешнего кода память должна выделяться с помощью стандартных для C механизмов (malloc) и освобождаться с их же помощью (free). Обратиться к функции выделения памяти можно (и, как правило, нужно) в конструкторе класса, а самое правильное место для вызова внешней функции освобождения памяти — как раз финализатор, так как это расположение гарантирует, что память для внешнего кода будет выделена до использования объекта (при его создании) и освобождена только после прекращения его использования. Если финализатор будет вызван не сразу или даже не будет вызван вообще — ничего страшного не случится, поскольку выделенная внешняя память всё равно будет возвращена системе по завершении работы программы автоматически.
Другой удачный способ применения финализатора — проверка очистки объекта перед удалением. Если объект при создании или по ходу своей работы захватывает ценные системные ресурсы, кроме памяти (открывает файлы или коммуникационные каналы, подключается к устройствам ввода-вывода), то, очевидно, в момент удаления объекта сборщиком мусора все эти ресурсы должны быть уже освобождены (то есть объект должен быть очищен). Ошибки очистки (когда объект в некоторых ситуациях не очищается или, что ещё хуже, очищается не полностью) очень коварны, их сложно выявлять, так как проявляются они при выполнении совсем не той части кода, где допущена ошибка. Как уже говорилось, проводить очистку в финализаторе неразумно, так как неизвестно, когда он будет вызван и вызовется ли вообще. Зато в финализаторе вполне уместно и удобно провести проверку того, полностью ли очищен объект, и выдать, в той или иной форме, сообщение об ошибке, если какой-то ресурс остался захваченным. Неважно, что финализатор может быть вызван поздно и не каждый раз; всё равно, если ошибка очистки объекта имеется, то рано или поздно финализатор «поймает» её.
Создаваться финализаторы могут по-разному. В некоторых языках финализатор является частью стандартной библиотеки. Обычно в таких случаях он является виртуальным методом стандартного корневого класса, потомками которого являются все остальные классы в системе (в Java это метод finalize() класса Object). Могут финализаторы объявляться и с помощью специального синтаксиса. В C# синтаксис объявления финализатора позаимствован от деструкторов C++ — финализатором для класса Class становится метод с сигнатурой ~Class(). В языке Nemerle, построенном на основе C#, от этого синтаксиса отказались, поскольку сочли его провоцирующим ошибки.