Вы не должны заменять все методы finalize()
на Cleaner
. Тот факт, что отказ от метода finalize()
и введение (public
) Cleaner
произошло в одной и той же версии Java, указывает только на то, что была проведена общая работа над этой темой, а не на то, что одно должно заменить другое.
Другая связанная с этой версией Java работа — удаление правила, согласно которому PhantomReference
не очищается автоматически (да, до Java 9 использование PhantomReference
вместо finalize()
по-прежнему требовало двух циклов GC для восстановления объекта) и введение Reference.reachabilityFence(…)
.
Первая альтернатива finalize()
состоит в том, чтобы вообще не иметь операции, зависящей от сборки мусора. Хорошо, когда вы говорите, что у вас не так много, но я видел в дикой природе совершенно устаревшие finalize()
методы. Проблема в том, что finalize()
выглядит как обычный метод protected
, а живучий миф о том, что finalize()
был каким-то деструктором, до сих пор распространяется на некоторых интернет-страницах. Пометка deprecated позволяет сообщить разработчику, что это не так, не нарушая совместимости. Использование механизма, требующего явной регистрации, помогает понять, что это не нормальный ход выполнения программы. И это не больно, когда это выглядит сложнее, чем переопределение одного метода.
Если ваш класс инкапсулирует ресурс не из кучи, документация гласит:
Классы, экземпляры которых содержат ресурсы, не относящиеся к куче, должны предоставлять метод для явного освобождения этих ресурсов, а также должны реализовывать AutoCloseable, если это необходимо.
(так что это предпочтительное решение)
очиститель и PhantomReference обеспечивают более гибкие и эффективные способы высвобождения ресурсов, когда объект становится недоступным.
Поэтому, когда вам действительно нужно взаимодействие со сборщиком мусора, даже в этом кратком комментарии к документации указаны две альтернативы, поскольку PhantomReference
здесь не упоминается как скрытая от разработчика внутренняя часть Cleaner
; использование PhantomReference
напрямую является альтернативой Cleaner
, которая может быть еще более сложной в использовании, но также обеспечивает еще больший контроль над синхронизацией и потоками, включая возможность очистки в том же потоке, который использовал ресурс. (Сравните с WeakHashMap
, в котором такая очистка позволяет избежать затрат на потокобезопасные конструкции). Это также позволяет обрабатывать исключения, возникающие во время очистки, лучше, чем просто проглатывать их.
Но даже Cleaner
решает больше проблем, о которых вы знаете.
Существенной проблемой является время регистрации.
Объект класса с нетривиальным методом finalize()
регистрируется при выполнении конструктора Object()
. На данный момент объект еще не инициализирован. Если ваша инициализация завершается с исключением, метод finalize()
все равно будет вызываться. Может возникнуть соблазн решить это с помощью данных объекта, например. установка флага initialized
в true
, но вы можете сказать это только для своих собственных данных экземпляра, но не для данных подкласса, который все еще не был инициализирован, когда ваш конструктор возвращается.
Для регистрации очистителя требуется полностью построенный Runnable
, содержащий все необходимые данные для очистки, без привязки к строящемуся объекту. Вы даже можете отложить регистрацию, если в конструкторе не произошло выделение ресурсов (подумайте о несвязанном экземпляре Socket
или экземпляре Frame
, который атомарно не подключен к дисплею).
Метод finalize()
можно переопределить, не вызывая метод суперкласса или не делая этого в исключительном случае. Предотвращение переопределения метода путем объявления его final
вообще не позволяет подклассам выполнять такие действия по очистке. Напротив, каждый класс может регистрировать уборщиков, не мешая другим уборщикам.
Конечно, вы могли бы решить такие проблемы с инкапсулированными объектами, однако дизайн с использованием метода finalize()
для каждого класса ведет в другом, неправильном направлении.
Как вы уже обнаружили, существует метод clean()
, который позволяет немедленно выполнить действие по очистке и удалить очиститель. Таким образом, при предоставлении явного метода закрытия или даже при реализации AutoClosable
это предпочтительный способ очистки, своевременного удаления ресурса и избавления от всех проблем очистки на основе сборщика мусора.
Обратите внимание, что это согласуется с пунктами, упомянутыми выше. Для объекта может быть несколько очистителей, например. зарегистрированы разными классами в иерархии. Каждый из них может запускаться индивидуально, с внутренним решением в отношении прав доступа, только тот, кто зарегистрировал очиститель, получает доступ к связанному Cleanable
, чтобы иметь возможность вызывать метод clean()
.
Тем не менее, часто упускается из виду, что самое худшее, что может случиться при управлении ресурсами с помощью сборщика мусора, — это не то, что действие очистки может выполняться позже или вообще никогда не выполняться. Худшее, что может случиться, это то, что он запускается слишком рано. См., например, finalize(), вызываемый для сильно достижимого объекта в Java 8. Или, действительно хороший, JDK-8145304, Executors.newSingleThreadExecutor().submit(runnable) выбрасывает RejectedExecutionException, где финализатор завершает работу все еще используемой службы-исполнителя.
Конечно, просто использование Cleaner
или PhantomReference
не решает эту проблему. Но удаление финализаторов и реализация альтернативного механизма, когда это действительно необходимо, — это возможность тщательно обдумать тему и, возможно, добавить reachabilityFence
s, где это необходимо. Худшее, что вы можете иметь, — это метод, который выглядит простым в использовании, когда на самом деле тема ужасно сложна, и 99% его использования потенциально могут когда-нибудь сломаться.
Кроме того, хотя альтернативы более сложны, вы сами сказали, что они редко нужны. Эта сложность должна затрагивать только часть вашей кодовой базы. Любые, почему java.lang.Object
, базовый класс для всех классов, должен содержать метод, обращающийся к редкому угловому случаю программирования Java?
person
Holger
schedule
19.10.2018
finalize()
. В Javadoc явно говорится, что Cleaner и PhantomReference предоставляют более гибкие и эффективные способы высвобождения ресурсов, когда объект становится недоступным. - person Elliott Frisch   schedule 18.10.2018