Эффективная Java говорит:
Существует серьезное снижение производительности при использовании финализаторов.
Почему медленнее уничтожать объект с помощью финализаторов?
Эффективная Java говорит:
Существует серьезное снижение производительности при использовании финализаторов.
Почему медленнее уничтожать объект с помощью финализаторов?
Из-за того, как работает сборщик мусора. Для повышения производительности большинство Java GC используют копирующий сборщик, в котором короткоживущие объекты размещаются в «райском» блоке памяти, и когда приходит время собирать это поколение объектов, GC просто нужно скопировать объекты, которые все еще «живы» в более постоянном пространстве для хранения, а затем он может стереть (освободить) весь блок памяти «рай» сразу. Это эффективно, потому что большая часть Java-кода будет создавать многие тысячи экземпляров объектов (упакованных примитивов, временных массивов и т. д.) с временем жизни всего несколько секунд.
Однако, когда у вас есть финализаторы, сборщик мусора не может просто стереть сразу все поколение. Вместо этого ему нужно определить все объекты в этом поколении, которые необходимо финализировать, и поставить их в очередь в потоке, который фактически выполняет финализаторы. Тем временем сборщик мусора не может эффективно завершить очистку объектов. Таким образом, он либо должен поддерживать их жизнь дольше, чем они должны быть, либо должен откладывать сбор других объектов, либо и то, и другое. Кроме того, у вас есть произвольное время ожидания фактического выполнения финализаторов.
Все эти факторы в сумме приводят к значительным потерям во время выполнения, поэтому обычно предпочтительнее использовать детерминированную финализацию (используя метод close()
или аналогичный для явной финализации состояния объекта).
FileOutputStream
. Таким образом, маловероятно, что финализаторы для некоторых объектов будут задерживать GC объектов, не использующих финализаторы, потому что это повлияет на большинство программ.
- person Raedwald; 01.08.2014
FileOutputStream
в OpenJDK имеет финализатор, который вы можете увидеть, просмотрев исходный код OpenJDK. (Однако я не могу найти ничего, что требует реализации стандартной библиотеки для использования финализаторов.) Таким образом, на практике объекты, которые в остальном подходят для GC, но все еще ожидают финализации, просто повышаются до следующее более старое поколение (выжившее пространство или стационарное), пока финализатор стоит в очереди на запуск. Но фактическая память не будет восстановлена до тех пор, пока в следующий раз не будет собрано следующее более старое поколение.
- person Daniel Pryden; 01.08.2014
close()
и finalize()
, возникают ли эти накладные расходы, если мы вызвали close()
явно?
- person Gerardo Cauich; 26.06.2021
На самом деле столкнулся с одной такой проблемой:
В Sun HotSpot JVM финализаторы обрабатываются в потоке с фиксированным низким приоритетом. В приложении с высокой нагрузкой легко создавать объекты, требующие финализации, быстрее, чем поток финализации с низким приоритетом может их обработать. При этом пространство в куче, используемое объектами, ожидающими финализации, недоступно для других целей. В конце концов, ваше приложение может тратить все свое время на сборку мусора, потому что вся доступная память используется объектами, ожидающими финализации.
Это, конечно, помимо многих других причин не использовать финализаторы, которые описаны в разделе «Эффективная Java».
Я только что взял со стола книгу «Эффективная 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.
Я бы порекомендовал прочитать этот раздел полностью - он объясняет вещи намного лучше, чем я могу повторять здесь.
Если вы прочитали документацию finalize() вы заметите, что финализаторы позволяют объекту предотвратить сбор сборщиком мусора.
Если финализатора нет, объект можно просто удалить, и ему не нужно больше внимания. Но если есть финализатор, то его нужно потом проверить, не стал ли объект снова "видимым".
Не зная точно, как реализована текущая сборка мусора Java (на самом деле, поскольку есть разные реализации Java, есть и разные сборщики мусора), можно предположить, что сборщик мусора должен выполнять какую-то дополнительную работу, если у объекта есть финализатор, потому что этой функции.
Моя мысль такова: Java — это язык со сборщиком мусора, который освобождает память на основе собственных внутренних алгоритмов. Время от времени сборщик мусора сканирует кучу, определяет, на какие объекты больше нет ссылок, и освобождает память. Финализатор прерывает это и принудительно освобождает память вне цикла GC, что может привести к неэффективности. Я думаю, что лучше всего использовать финализаторы только тогда, когда это АБСОЛЮТНО необходимо, например, для освобождения дескрипторов файлов или закрытия соединений с БД, что должно выполняться детерминировано.
Одна из причин, которую я могу придумать, заключается в том, что явная очистка памяти не нужна, если все ваши ресурсы представляют собой объекты Java, а не собственный код.