Пожалуй наиболее противоречивая и странно написанная часть The Java Language Specification - это часть про финализацию. Какие возникают вопросы и проблемы и почему?
Что такое финализация? Это вызов метода Object.finalize(), в котором можно попытаться сделать некоторые действия после того, как сборщик мусора определил, что объект недостижим, но перед тем, как память будет на самом деле освобождена.
Вот только некоторые странные вещи, которые на первый взгляд могут не совпасть с нашим интуитивным представлением.
-
Момент, когда вызовется метод
finalize()четко не определен. На самом деле, в спецификации не гарантируется, что он воообще будет когда-нибудь вызван, даже если объект стал недостижим. Когда вызов может происходить зависит от реализации JVM. -
Метод
finalize()не вызывает автоматическиfinalize()предка. Для того, чтобы это произошло, необходимо явно его вызвать:super.finalize(). -
Метод
finalize()будет вызван асинхронно, т.е. скорее всего в другом потоке. В текущей реализации Sun JVM для его обработки используется специальный поток "Finalizer", который последовательно вызывает финализаторы. Спецификация не ограничивает то, в каких потоках он может выполнятся или сколько может быть этих потоков. Единственное требование — это то, что этот поток не будет держать какую-нибудь блокировку. В одной из первых реализации JVM это разрешалось и приводило к странным эффектам. -
Ссылка на объект (
this) доступна во время выполненияfinalize(). Это означает, что потенциально объект может снова стать достижим. В этом случае методfinalize()никогда не будет вызван повторно. -
Объекты с нетривиальным методом
finalize()обрабатываются сборщиком мусора особым образом. После того, как этот метод выполнился, сборщик мусора должен вновь определить что объект достижим. Это создает дополнительную нагрузку. Кроме того, в текущей реализации Sun JVM для поддержки используются дополнительные объекты FinalReference и Finalizer, что сказывается на объеме используемой памаять и производительности. -
То, что
finalize()может исполняться в нескольких потоках параллельно, означает, что во многих случаях стоит позаботиться о синхронизации. В большинстве случаев нетривиальный методfinalize()будет использовать какие-либо разделяемые ресурсы. -
Порядок, в котором
finalize()будет вызван не определен. Если у объекта A есть поле, которое ссылается на объект B и эти объекты стали недостижимы, тоfinalize()для A может быть вызван и до, после или одновременно сfinalize()для B. Если B используется вfinalize()для A, то его состояние во время выполнения не определено. Кроме того, это состояние может меняться во время выполненияfinalize()! -
Спецификация не ограничивает, какие оптимизации могут применяться. Может так случиться, что
finalize()будет вызван, после начала выполнения какого-либо метода, но но перед его завершением. Это иногда может стать проблемой, поскольку вызванный методfinalize()может невовремя изменить состояние поля объекта. Хорошим примером является 6295525. -
Финализация классов — когда-то в языке Java была и такая вещь.
Для чего может использоваться финализация?
-
Для отладки. В этом случае в конструктор и
finalize()просто выводятся отладочные сообщения. Это использование совершенно законно. -
В C++ есть прием Получение ресурса есть инициализация(RAI, Resource Acquisition is Initialization).
При этом в конструкторе происходит инициализация, в деструкторе деинициализация.
Одной из причин, почему он не работает в Java является именно недетерминированность и асинхронность вызова
finalize(). Что же делать? Одним из решений является использование внутренних классов. Этот прием, например, активно используется в популярной J2EE библиотеке Spring Framework. Вот пример:transactionManager.execute(new TransactionCallback() { public Object doInTransaction(TransactionStatus status) { ... // Some actions that execute in one transaction } });Транзакция автоматически открывается при входе в doInTransaction и закрывается (подтверждается или откатывается) при выходе. При этом, данный код также корректно работает при возникновении исключений. -
Освобождение ресурсов, которые напрямую не связаны с Java объектами. Обычно это ресурсы операционной системы, например файлы,
графические ресурсы и т.д. В этом случае освобождение ресурса, которое обычно реализовано native методом должно произойти после
того, как этот объект больше не используется, т.е. когда он не достижим. В данном случае использование финализаторов иногда
оправдано. Однако, если количество возможных ресурсов ограничено, а памяти для Java много, то могут сборщик мусора может
просто не вызывать
finalize(), в то время как ресурсов будет недостаточно. Это делает использованиеfinalize()в данном случае также неоправданным. Если нет сложных структур данных, в которых эти объекты используются или то, как объект используется четко определено, то можно использовать специальный метод, который нужно будет явно вызывать.
Н.Х.
опубликовал vmrobot ( май 16 2006, 12:52:48 AM MSD ) Permalink Комментарии [5]

опубликовал denis Май 18, 2006 at 04:37 PM MSD #
опубликовал Nicolay Haustov Май 18, 2006 at 07:26 PM MSD #
опубликовал Blazkowicz Декабрь 27, 2006 at 05:54 PM MSK #
Когда-то я писАл Connection Pool для базы данных. Если юзер забыл вернуть коннекшн в пул, за него это делал GC. Коннекшн имел переопределенную finalize(), которая закрывала ресурсы (транзакции, операторы), возвращала коннекшн обратно в pool и писала в лог сее событие. Таким образом, юзерский код не давал сбоев, а о забытых коннекшнах можно было посмотреть в логе.
опубликовал null Август 09, 2007 at 01:34 PM MSD #
В случае закрытия ресурсов:
Следует учитывать, что в Sun JDK если код методна finalize() зациклится, то никто из finalizer-ов больще не выполнится.
Память, занятая объектами в очереди на финалицию остается занятой.
Кроме того, момент финализации заранее неизвестен, так что может быть ситуация, когда кол-во соединений превысит лимит, например, сервера и для нового соединения надо будет ждать пока JVM финализирует старое.
опубликовал Kirill Shirokov Август 09, 2007 at 02:26 PM MSD #