Почему финализаторы сильно снижают производительность?

Эффективная Java говорит:

Существует серьезное снижение производительности при использовании финализаторов.

Почему медленнее уничтожать объект с помощью финализаторов?


person unj2    schedule 18.05.2010    source источник
comment
Вам может понравиться эта статья, в ней рассказывается о том, как финализаторы могут снова сделать объекты доступными и т. д. Она также показывает, почему композиция может спасти положение (вместо наследования реализации) в некоторых случаях: java.sun.com/developer/technicalArticles/javase/finalization   -  person SyntaxT3rr0r    schedule 19.05.2010


Ответы (6)


Из-за того, как работает сборщик мусора. Для повышения производительности большинство Java GC используют копирующий сборщик, в котором короткоживущие объекты размещаются в «райском» блоке памяти, и когда приходит время собирать это поколение объектов, GC просто нужно скопировать объекты, которые все еще «живы» в более постоянном пространстве для хранения, а затем он может стереть (освободить) весь блок памяти «рай» сразу. Это эффективно, потому что большая часть Java-кода будет создавать многие тысячи экземпляров объектов (упакованных примитивов, временных массивов и т. д.) с временем жизни всего несколько секунд.

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

Все эти факторы в сумме приводят к значительным потерям во время выполнения, поэтому обычно предпочтительнее использовать детерминированную финализацию (используя метод close() или аналогичный для явной финализации состояния объекта).

person Daniel Pryden    schedule 18.05.2010
comment
Это, конечно, касается проблем с коллектором поколений. Другие стратегии GC имеют другие проблемы. Но все они сводятся к тому, что сборщик мусора должен выполнить дополнительную работу, в том числе как минимум два прохода по объекту, чтобы освободить его; один, чтобы добавить его в очередь финализации, и один, чтобы фактически освободить его после финализации. - person Lawrence Dol; 19.05.2010
comment
Буду ли я прав, думая, что несколько часто используемых классов Java API имеют финализаторы для освобождения ресурсов операционной системы? Я думаю о FileOutputStream. Таким образом, маловероятно, что финализаторы для некоторых объектов будут задерживать GC объектов, не использующих финализаторы, потому что это повлияет на большинство программ. - person Raedwald; 01.08.2014
comment
@Рэдвальд: Верно. Например, реализация FileOutputStream в OpenJDK имеет финализатор, который вы можете увидеть, просмотрев исходный код OpenJDK. (Однако я не могу найти ничего, что требует реализации стандартной библиотеки для использования финализаторов.) Таким образом, на практике объекты, которые в остальном подходят для GC, но все еще ожидают финализации, просто повышаются до следующее более старое поколение (выжившее пространство или стационарное), пока финализатор стоит в очереди на запуск. Но фактическая память не будет восстановлена ​​до тех пор, пока в следующий раз не будет собрано следующее более старое поколение. - person Daniel Pryden; 01.08.2014
comment
Предполагая, что объект имеет обе реализации, close() и finalize(), возникают ли эти накладные расходы, если мы вызвали close() явно? - person Gerardo Cauich; 26.06.2021

На самом деле столкнулся с одной такой проблемой:

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

Это, конечно, помимо многих других причин не использовать финализаторы, которые описаны в разделе «Эффективная Java».

person Sbodd    schedule 18.05.2010

Я только что взял со стола книгу «Эффективная Java», чтобы посмотреть, о чем он говорит.

Если вы прочтете главу 2, раздел 6, он подробно расскажет о различных хитах исполнения.

You can't know when the finalizer will run, or even if it will at all. Because those resources may never be claimed, you will have to run with fewer resources.

Я бы порекомендовал прочитать этот раздел полностью - он объясняет вещи намного лучше, чем я могу повторять здесь.

person corsiKa    schedule 18.05.2010

Если вы прочитали документацию finalize() вы заметите, что финализаторы позволяют объекту предотвратить сбор сборщиком мусора.

Если финализатора нет, объект можно просто удалить, и ему не нужно больше внимания. Но если есть финализатор, то его нужно потом проверить, не стал ли объект снова "видимым".

Не зная точно, как реализована текущая сборка мусора Java (на самом деле, поскольку есть разные реализации Java, есть и разные сборщики мусора), можно предположить, что сборщик мусора должен выполнять какую-то дополнительную работу, если у объекта есть финализатор, потому что этой функции.

person Simon Lehmann    schedule 18.05.2010
comment
Действительно, на этой странице также упоминается, что JVM по-разному обрабатывает объекты с нетривиальными финализаторами: fasterj.com/ статьи/finalizer2.shtml - person Gerardo Cauich; 26.06.2021

Моя мысль такова: Java — это язык со сборщиком мусора, который освобождает память на основе собственных внутренних алгоритмов. Время от времени сборщик мусора сканирует кучу, определяет, на какие объекты больше нет ссылок, и освобождает память. Финализатор прерывает это и принудительно освобождает память вне цикла GC, что может привести к неэффективности. Я думаю, что лучше всего использовать финализаторы только тогда, когда это АБСОЛЮТНО необходимо, например, для освобождения дескрипторов файлов или закрытия соединений с БД, что должно выполняться детерминировано.

person Jeremy    schedule 18.05.2010
comment
Действительно ли оно заставляет это делать или просто предлагает? - person corsiKa; 18.05.2010
comment
В основном правильно, но финализаторы не вызывают освобождение памяти вне цикла GC. Вместо этого, если сборщик мусора определяет, что объект должен быть финализирован, он воскрешает его и удерживает объект от сбора до тех пор, пока финализатор не будет выполнен. Но это может занять некоторое время, поскольку финализаторы (IIRC) не запускаются до следующего сбора постоянного поколения. - person Daniel Pryden; 18.05.2010
comment
Я считаю, что наилучшей практикой является использование финализаторов только тогда, когда АБСОЛЮТНО необходимо, например, при освобождении файловых дескрипторов или закрытии соединений с БД: обратите внимание, что именно в этом случае финализаторы не подходят, потому что финализатор может запускаться произвольно поздно или не вовремя. все. - person sleske; 04.10.2010

Одна из причин, которую я могу придумать, заключается в том, что явная очистка памяти не нужна, если все ваши ресурсы представляют собой объекты Java, а не собственный код.

person Amir Afghani    schedule 18.05.2010