Влияние финализаторов на производительность JVM

Согласно этой публикации в .Net,

Финализаторы на самом деле еще хуже. Помимо того, что они выполняются с опозданием (что действительно является серьезной проблемой для многих видов ресурсов), они также менее эффективны, поскольку могут выполнять только подмножество операций, разрешенных в деструкторе (например, финализатор не может надежно использовать другие объекты, тогда как деструктор может), и даже при записи в этом подмножестве финализаторы чрезвычайно сложно написать правильно. А сбор финализируемых объектов обходится дорого: каждый финализируемый объект и потенциально огромный граф объектов, достижимых из него, продвигаются к следующему поколению сборщика мусора, что удорожает сбор в несколько раз.

Относится ли это также к JVM вообще и к HotSpot в частности?


person Alexey Romanov    schedule 02.06.2010    source источник
comment
хотя и не совсем по теме (отсюда комментарий, а не ответ), вот отличная и не слишком старая (2007 г.) статья о потенциальных проблемах с доработкой java.sun.com/developer/technicalArticles/javase/finalization В качестве бонуса в статье показан еще один случай, когда использование наследования реализации вместо композиции приводит к проблемам, меньше всего их ожидаешь.   -  person NoozNooz42    schedule 02.06.2010


Ответы (4)


Вот несколько избранных цитат из Effective Java 2nd Edition: Пункт 7. Избегайте финализаторов:

Финализаторы непредсказуемы, часто опасны и, как правило, не нужны. Их использование может вызвать нестабильное поведение, низкую производительность и проблемы с переносимостью. Финализаторы имеют мало допустимых применений, [...] как правило, вам следует избегать финализаторов.

Вы действительно должны убедиться, что на самом деле вам нужны финализаторы; большую часть времени вы НЕ ДЕЛАЕТЕ.

Программистов на C++ предупреждают, чтобы они не думали о финализаторах как о Java-аналоге деструкторов C++. В C++ деструкторы — это обычный способ освобождения ресурсов, связанных с объектом, необходимый аналог конструкторов. В Java сборщик мусора освобождает память, связанную с объектом, когда он становится недоступным, не требуя особых усилий со стороны программиста. Деструкторы C++ также используются для освобождения других ресурсов, не связанных с памятью. В Java для этой цели обычно используется блок try-finally.

Также важно учитывать семантику когда финализаторов:

JLS 12.6 Завершение экземпляров класса

Язык программирования Java не определяет, как скоро будет вызван finalizer [...ни] какой поток будет вызывать финализатор для любого данного объекта. [...] Если во время завершения возникает неперехваченное исключение, исключение игнорируется, и завершение этого объекта завершается. (JLS 12.6.2) Вызовы Finalizer не Заказал

Более того, сломан единственный механизм запуска финализатора по требованию. Следующие цитаты взяты из Effective Java 2nd Edition:

[...] Единственные методы, которые утверждают, что гарантируют завершение, - это System.runFinalizersOnExit и его злой близнец, Runtime.runFinalizersOnExit. Эти методы фатально ошибочны и устарели.

Блох пошел дальше, комментируя штраф за производительность (выделено его):

Да, и еще кое-что: использование финализаторов приводит к серьезному снижению производительности. На моей машине время создания и уничтожения простого объекта составляет около 5,6 нс. Добавление финализатора увеличивает время до 2400 нс. Другими словами, создание и уничтожение объектов с финализаторами происходит примерно в 430 раз медленнее.

С такими небольшими подробностями о методологии бенчмаркинга, я не думаю, что конкретные цифры много значат, но они подтверждают то, что было широко задокументировано: финализаторы очень дороги.

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

person polygenelubricants    schedule 02.06.2010
comment
Вопрос об этих цифрах и причинах затрат был поднят позже, здесь есть объяснение. Ответ на вопрос, применимо ли это ко всем JVM, прост: поскольку требуется JVM, которая обнаруживает классы, которые не переопределяют finalize(), для оптимизации этих случаев JVM, не имеющие такой оптимизации, будут обрабатывать все объекты одинаково, так же медленно, как если бы они иметь собственный метод finalize(). Но любая разумная JVM имеет эту оптимизацию (или вообще не поддерживает финализацию). - person Holger; 04.07.2018

Вот явное заявление от 2004 года:

Объекты с финализаторами (те, которые имеют нетривиальный метод finalize()) имеют значительные накладные расходы по сравнению с объектами без финализаторов, и их следует использовать с осторожностью. Финализируемые объекты медленнее выделяются и медленнее собираются. Во время выделения JVM должна зарегистрировать любые финализируемые объекты в сборщике мусора, и (по крайней мере, в реализации HotSpot JVM) финализируемые объекты должны следовать более медленному пути выделения, чем большинство других объектов. Точно так же финализируемые объекты собираются медленнее. Требуется не менее двух циклов сборки мусора (в лучшем случае), прежде чем финализируемый объект может быть утилизирован, и сборщику мусора приходится выполнять дополнительную работу, чтобы вызвать финализатор. В результате больше времени затрачивается на выделение и сбор объектов, а сборщик мусора больше загружается, потому что память, используемая недостижимыми финализируемыми объектами, сохраняется дольше. Прибавьте к этому тот факт, что финализаторы не гарантированно будут работать в предсказуемые сроки или вообще не будут запускаться, и вы увидите, что существует относительно немного ситуаций, в которых финализаторы являются правильным инструментом для использования.

person Alexey Romanov    schedule 02.06.2010

Java имеет очень похожий механизм финализаторов. Вот хорошая статья о финализаторах: http://www.javaworld.com/javaworld/jw-06-1998/jw-06-techniques.html

Общее эмпирическое правило в Java — не использовать их.

person Romain Hippeau    schedule 02.06.2010
comment
@Romain Hippeau: мне очень нравится ссылка на статью 12-летней давности :) - person NoozNooz42; 02.06.2010
comment
На самом деле, меня интересует именно вариант использования, который статья считает приемлемым: но вы также можете включить финализатор, который проверяет, был ли ресурс уже освобожден, и если это не так, он идет вперед и освобождает его. Такой финализатор защищает от (и, надеюсь, не поощряет) небрежного использования вашего класса. - person Alexey Romanov; 02.06.2010
comment
@Alexey - если вам действительно нужно для чего-то использовать финализаторы, то вам нужно их использовать, даже если они довольно дорогие. Они не НАСТОЛЬКО дороги... при условии, что вы не создаете тысячи финализируемых объектов. - person Stephen C; 02.06.2010
comment
@ Стивен С. Если вам действительно нужно использовать финализаторы для чего-то, вам нужно сделать что-то еще, потому что нет гарантии, что финализатор вообще будет запущен. - person emory; 02.06.2010
comment
@NoozNooz42 Через несколько лет это будет статья двадцатилетней давности :( - person Romain Hippeau; 02.06.2010
comment
@ Алексей У меня создалось впечатление, что вы пытаетесь оправдать их использование. Они плохие не только с точки зрения производительности, но и если в финализаторе возникнет исключение, оно никогда не будет вызвано. Также внутри них можно создавать утечки памяти. НЕ ПОЛЬЗУЙТЕСЬ ИМ!!!! Вместо этого используйте языковую конструкцию try/finally. - person Romain Hippeau; 02.06.2010

finalize() Вызывается сборщиком мусора для объекта, когда сборщик мусора определяет, что больше нет ссылок на объект. Подкласс переопределяет метод finalize, чтобы избавиться от системных ресурсов или выполнить другую очистку.

Не используйте финализаторы, главным образом потому, что они непредсказуемы и мы не знаем когда они будут выполнены, "не пытайтесь быть умнее JVM"

person Jorgesys    schedule 30.01.2014