Основными двумя аргументами против переопределения Object.finalize()
является то, что:
Вы не можете решить, когда это называется.
Это не может быть вызвано вообще.
Если я правильно понимаю, я не думаю, что это достаточно веские причины, чтобы Object.finalize()
так сильно ненавидеть .
Именно реализация виртуальной машины и GC должны определить, когда подходящее время для освобождения объекта, а не разработчик. Почему так важно решить, когда вам
Object.finalize()
позвонят?Обычно, и поправьте меня, если я ошибаюсь, единственное время
Object.finalize()
, когда меня не вызвали, - это когда приложение было закрыто до того, как GC получил шанс на запуск. Однако объект все равно освобождается, когда процесс приложения завершается с ним. ТакObject.finalize()
что не позвонили, потому что не нужно было звонить. Почему разработчик заботится?
Каждый раз, когда я использую объекты, которые мне приходится закрывать вручную (например, дескрипторы файлов и соединения), я очень расстраиваюсь. Я должен постоянно проверять, есть ли у объекта реализация close()
, и я уверен, что пропустил несколько обращений к нему в некоторые моменты в прошлом. Почему не проще и безопаснее просто предоставить виртуальной машине и сборщику мусора распоряжение этими объектами путем внедрения close()
реализации Object.finalize()
?
источник
finalize()
немного запутана. Если вы когда-либо реализуете его, убедитесь, что он поточно-ориентирован по отношению ко всем остальным методам того же объекта.Ответы:
По моему опыту, есть одна и только одна причина переопределения
Object.finalize()
, но это очень веская причина :Статический анализ может выявлять пропуски только в сценариях тривиального использования, а предупреждения компилятора, упомянутые в другом ответе, имеют настолько упрощенное представление о вещах, что вам действительно нужно отключить их, чтобы выполнить что-то нетривиальное. (У меня включено намного больше предупреждений, чем у любого другого программиста, о котором я знаю или когда-либо слышал, но у меня не включены глупые предупреждения.)
Финализация может показаться хорошим механизмом для обеспечения того, чтобы ресурсы не оставались нераспределенными, но большинство людей воспринимают это совершенно неправильно: они думают об этом как о альтернативном резервном механизме, защите «второго шанса», которая автоматически спасет день, избавившись от ресурсов, которые они забыли. Это совершенно неправильно . Должен быть только один способ сделать любую вещь: либо вы всегда закрываете все, либо финализация всегда закрывает все. Но поскольку завершение ненадежно, оно не может быть завершением.
Итак, есть эта схема, которую я называю Mandatory Disposal , и она предусматривает, что программист всегда отвечает за явное закрытие всего, что реализует
Closeable
илиAutoCloseable
. (Оператор try-with-resources по-прежнему считается явным закрытием.) Конечно, программист может забыть, так что вот когда финализация вступает в игру, но не как волшебная фея, которая волшебным образом исправит ситуацию в конце: если финализация обнаружит этоclose()
не было вызвано, это непопытаться вызвать его именно потому, что (с математической уверенностью) найдутся орды программистов n00b, которые будут полагаться на него, чтобы выполнять работу, которую они слишком ленивы или слишком рассеянны. Таким образом, при обязательном удалении, когда финализация обнаруживает, чтоclose()
не было вызвано, она записывает ярко-красное сообщение об ошибке, сообщая программисту большими жирными заглавными буквами, чтобы он исправил свои ошибки.В качестве дополнительного преимущества, ходят слухи, что «JVM будет игнорировать тривиальный метод finalize () (например, метод, который просто возвращает, ничего не делая, как тот, который определен в классе Object)», поэтому при обязательном удалении вы можете избежать всей финализации издержки во всей вашей системе ( см. ответ alip для получения информации о том, насколько ужасны эти издержки), кодируя ваш
finalize()
метод следующим образом:Идея заключается в том, что
Global.DEBUG
этоstatic final
переменная, значение которой известно во время компиляции, поэтому, если это так,false
то компилятор вообще не будет генерировать никакого кода для всегоif
оператора, что сделает этот тривиальный (пустой) финализатор, который, в свою очередь, означает, что ваш класс будет обрабатываться так, как будто у него нет финализатора. (В C # это было бы сделано с хорошим#if DEBUG
блоком, но что мы можем сделать, это Java, где мы платим очевидную простоту в коде с дополнительными издержками в мозге.)Подробнее о принудительном удалении, с дополнительным обсуждением об утилизации ресурсов в dot Net, здесь: michael.gr: Обязательное удаление против мерзости "избавляйся от утилизации"
источник
close()
, на всякий случай. Я считаю, что если моим испытаниям нельзя доверять, то лучше не выпускать систему в производство.if( Global.DEBUG && ...
конструкция работала так, чтобы JVM игнорировалаfinalize()
метод как тривиальный,Global.DEBUG
должна быть установлена во время компиляции (в отличие от вставленной и т. Д.), Поэтому последующий код будет мертвым. Вызоваsuper.finalize()
вне блока if также достаточно, чтобы JVM восприняла его как нетривиальное (независимо от значенияGlobal.DEBUG
, по крайней мере, в HotSpot 1.8), даже если суперкласс#finalize()
тривиален!java.io
. Если такого типа безопасности потока не было в списке пожеланий, это добавляет к накладным расходам, вызваннымfinalize()
…Поскольку файловые дескрипторы и соединения (то есть файловые дескрипторы в системах Linux и POSIX) являются довольно редким ресурсом (в некоторых системах вы можете ограничиться 256 из них или 16384 в других; см. Setrlimit (2) ). Нет гарантии, что GC будет вызываться достаточно часто (или в нужное время), чтобы избежать исчерпания таких ограниченных ресурсов. И если GC недостаточно вызван (или финализация не выполняется в нужное время), вы достигнете этого (возможно, низкого) предела.
Финализация - это «лучшее из возможного» в JVM. Он может не вызываться или вызываться слишком поздно ... В частности, если у вас много ОЗУ или если ваша программа не выделяет много объектов (или если большинство из них умирает до того, как их перенаправляют на достаточно старый) генерация копирующим поколением GC), GC можно вызывать довольно редко, и финализация может выполняться не очень часто (или даже вообще не запускаться).
Так что
close
файловые дескрипторы явно, если это возможно. Если вы боитесь их утечки, используйте финализацию как дополнительную меру, а не как основную.источник
Посмотрите на ситуацию следующим образом: вы должны писать только код, который (а) является правильным (в противном случае ваша программа просто ошибочна) и (б) необходим (в противном случае ваш код слишком велик, что означает, что необходимо больше ОЗУ, больше циклов, потраченных на бесполезные вещи, больше усилий, чтобы понять это, больше времени, потраченного на поддержание этого и т. д. и т. д.)
Теперь рассмотрим, что именно вы хотите сделать в финализаторе. Либо это необходимо. В этом случае вы не можете поместить его в финализатор, потому что вы не знаете, будет ли он вызван. Это не достаточно хорошо. Или это не нужно - тогда вам не следует писать это в первую очередь! В любом случае, поместить его в финализатор - неправильный выбор.
(Обратите внимание, что примеры, которые вы называете, например закрытие файловых потоков, выглядят так, как будто они на самом деле не нужны, но они есть. Просто пока вы не достигнете предела количества открытых дескрипторов файлов в вашей системе, вы не заметите, что ваши код неверен. Но это ограничение является особенностью операционной системы и , следовательно , еще более непредсказуемым , чем политики JVM для финализаторов, так что на самом деле, на самом деле это важно , чтобы не тратить дескрипторы файлов.)
источник
finalize()
для закрытия в случае, если цикл gc действительно нуждается в освобождении БАРАН. В противном случае, я бы держал объект в оперативной памяти вместо того, чтобы восстанавливать и закрывать его каждый раз, когда мне нужно его использовать. Конечно, ресурсы, которые он открывает, не будут освобождены, пока объект не будет GCed, когда бы это ни было, но мне, возможно, не нужно гарантировать, что мои ресурсы будут освобождены в определенное время.Одна из главных причин не полагаться на финализаторы заключается в том, что большинство ресурсов, которые можно испытать при очистке в финализаторе, очень ограничены. Сборщик мусора запускается очень часто, так как обход ссылок для определения возможности выпуска чего-либо является дорогостоящим. Это означает, что может пройти некоторое время, прежде чем ваши объекты будут уничтожены. Например, если у вас есть много объектов, открывающих недолговечные соединения с базой данных, оставление финализаторов для очистки этих соединений может привести к исчерпанию пула соединений, пока он ожидает окончательного запуска сборщика мусора и освобождения завершенных соединений. Затем, из-за ожидания, вы получаете большое количество невыполненных запросов на очереди, которые снова быстро исчерпывают пул соединений. Это'
Кроме того, использование try-with-resources позволяет легко закрывать закрываемые объекты по завершении. Если вы не знакомы с этой конструкцией, я предлагаю вам проверить ее: https://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html
источник
В дополнение к тому, что предоставление финализатору ресурсов для освобождения ресурсов, как правило, является плохой идеей, финализируемые объекты снижают производительность.
Из теории и практики Java: сборка мусора и производительность (Брайан Гетц), финализаторы не ваш друг :
источник
finalize()
.Моя (наименьшая) любимая причина избегать
Object.finalize
- не то, что объекты могут быть завершены после того, как вы ожидаете этого, но они могут быть завершены до того, как вы ожидаете этого. Проблема не в том, что объект, который все еще находится в области видимости, может быть завершен до выхода из области действия, если Java решит, что он больше недоступен.Смотрите этот вопрос для более подробной информации. Еще более увлекательным является то, что это решение может быть принято только после того, как активируется оптимизация «горячей точки», что делает его болезненным для отладки.
источник
HasFinalize.stream
сам по себе должен быть отдельно завершаемым объектом. То есть завершениеHasFinalize
не должно завершаться или пытаться очиститьstream
. Или, если это должно, то это должно сделатьstream
недоступным.В Eclipse я получаю предупреждение всякий раз, когда забываю закрыть то, что реализует
Closeable
/AutoCloseable
. Я не уверен, является ли это Eclipse или частью официального компилятора, но вы можете использовать аналогичные инструменты статического анализа, чтобы помочь вам в этом. Например, FindBugs может помочь вам проверить, не забыли ли вы закрыть ресурс.источник
AutoCloseable
. Это упрощает управление ресурсами с помощью try-with-resources. Это сводит на нет несколько аргументов в вопросе.На ваш первый вопрос:
Что ж, JVM определит, когда стоит восстановить память, выделенную для объекта. Это не обязательно время, когда
finalize()
должен произойти очистка ресурса, в котором вы хотите выполнить . Это проиллюстрировано в вопросе «finalize (), вызываемый для сильно достижимого объекта в Java 8» в SO. Тамclose()
метод был вызванfinalize()
методом, в то время как попытка чтения из потока тем же объектом все еще ожидает. Таким образом, помимо известной возможности, котораяfinalize()
вызывается слишком поздно, есть вероятность, что она вызывается слишком рано.Предпосылка вашего второго вопроса:
это просто неправильно. Для JVM вообще не требуется поддержка финализации. Что ж, это не совсем неправильно, так как вы все равно можете интерпретировать это как «приложение было прервано до его завершения», предполагая, что ваше приложение когда-либо прекратит работу.
Но обратите внимание на небольшую разницу между «GC» вашего первоначального утверждения и термином «завершение». Сборка мусора отличается от завершения. Как только управление памятью обнаруживает, что объект недоступен, он может просто восстановить свое пространство, если либо у него нет специального
finalize()
метода, либо финализация просто не поддерживается, либо он может поставить объект в очередь для завершения. Таким образом, завершение цикла сборки мусора не означает, что финализаторы выполняются. Это может произойти позже, когда очередь обрабатывается, или вообще не обрабатывается.Этот момент также является причиной того, что даже на JVM с поддержкой финализации полагаться на него для очистки ресурсов опасно. Сборка мусора является частью управления памятью и, следовательно, запускается потребностями в памяти. Вполне возможно, что сборка мусора никогда не запускается, потому что в течение всего времени выполнения достаточно памяти (ну, это все равно вписывается в описание «приложение было прервано до того, как GC получил шанс запустить»). Также возможно, что GC действительно работает, но после этого освобождается достаточно памяти, поэтому очередь финализатора не обрабатывается.
Другими словами, нативные ресурсы, управляемые таким образом, все еще чужды управлению памятью. Хотя гарантируется, что an генерируется
OutOfMemoryError
только после достаточных попыток освободить память, это не относится к собственным ресурсам и завершению. Возможно, что открытие файла завершится неудачно из-за недостатка ресурсов, когда очередь финализации заполнена объектами, которые могут освободить эти ресурсы, если финализатор когда-либо запускался…источник