Java
17 August 2012

Опасности метода finalize

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

Итак, поехали от очевидного к менее интуитивному.
  1. Так как метод finalize вызывается при первой сборке мусора следующей за моментом когда ваш объект стал недостижим, то вполне реально, что он не будет вызван вообще, ведь ваше приложение может закончить свою работу так и не дойдя до этой самой сборки мусора. Хотя, конечно, есть один замечательный метод System.runFinalizersOnExit(true), вызвав который на старте программы, метод finalize все таки сработает у уже недостижимых объектах во время корректной остановки приложения.
  2. Спецификация JVM не определяет вопрос многопоточности метода финализации. В HotSpot все методы finalize будут вызываться последовательно в одном потоке Finalizer. Однако, если вы вызовете метод System.runFinalization(), то родится еще один поток, который заблокирует текущий и будет выполнять методы finalize, если подходящие объекты есть в очереди. Причем это вполне может происходить параллельно основному потоку Finalizer.
  3. Переопределение метода finalize значительно удлиняет время жизни объекта после смерти, так как он будет удален из памяти не раньше второй сборки мусора. А учитывая два первых пункта, если метод finalize у вас будет тяжелым и\или таких объектов будет очень много, то объекты могут довольно долго висеть в фазе финализации и продолжать занимать место в памяти.
  4. Во время выполнения метода finalize вы можете восстановить ссылку на объект, например, поместив ее в какой-нибудь статический контекст, тем самым вы воскресите объект. Опасность такого маневра заключается в том, что второй раз метод finalize у данного объекта уже никогда вызван не будет. Поэтому если вам по каким-то причинам очень надо воскресить данный объект, то лучше создавайте внутри метода finalize его копию.
  5. Одна из самых неприятных проблем возникающих при использовании метода finalize — это реордеринг. Представьте, что у вас есть два объекта с переопределенным методом finalize, один из которых ссылается на другой. Так вот, если эти объекты стали недостижимы, то порядок вызова методов финализации произойдет в случайном порядке. Таким образом, у вас будет потенциальная опасность вызвать какой-нибудь метод на уже финализированном объекте из метода finalize другого объекта и получить ошибку. Причем проблема будет возникать не на каждом объекте, что добавит головной боли при отладке.
  6. Согласно Джошуа Блоху, автору знаменитой книги «Effective Java: Programming Language Guide», для объектов с переопределенным методом finalize аллокация и сборка может происходить в 430 раз медленнее, чем у обычного объекта.
  7. Любые исключения выброшенные в теле метода будут проигнорированы.
  8. Надо не забыть в конце метода вызвать super.finalize (). А учитывая предыдущий пункт, сделать это необходимо в блоке finally.

Согласно всему вышесказанному по возможности следует избегать использование метода finalize, вернее не стоит полагаться на него. Лучше освобождать ресурсы программно, а в методе finalize логировать, если этого почему-то сделано не было, чтобы вовремя найти и починить возникшую проблему.
Если же вам все же надо освободить ресурсы именно при сборке объекта, то, вероятнее всего для этого лучше использовать фантомные ссылки.

+29
17.7k 134
Comments 19