Должен ли Java 9 Cleaner быть предпочтительнее финализации? - PullRequest
0 голосов
/ 18 октября 2018

В Java переопределение метода finalize получает плохой рэп, хотя я не понимаю, почему.Классы, подобные FileInputStream, используют его для обеспечения вызова close как в Java 8, так и в Java 10. Тем не менее, в Java 9 появился java.lang.ref.Cleaner, который использует механизм PhantomReference вместо финализации GC.Сначала я подумал, что это просто способ добавить доработку к сторонним классам.Тем не менее, пример, приведенный в его javadoc , показывает пример использования, который можно легко переписать с помощью финализатора.

Должен ли я переписывать все мои finalize методы в терминах Cleaner?(Конечно, у меня их немного. Просто некоторые классы, которые используют ресурсы ОС, особенно для взаимодействия CUDA.)

Как я могу сказать, Cleaner (через PhantomReference) избегает некоторых опасностей finalizer.В частности, у вас нет доступа к очищенному объекту, и поэтому вы не можете воскресить его или любое из его полей.

Однако это единственное преимущество, которое я вижу.Очиститель тоже нетривиален.Фактически, он и финализация используют ReferenceQueue!(Разве тебе не нравится, как легко читать JDK?) Это быстрее, чем финализация?Во избежание ожидания двух ГХ?Сможет ли он избежать исчерпания кучи, если многие объекты поставлены в очередь для очистки?(Мне кажется, что ответом на все эти вопросы будет «нет».)

Наконец, на самом деле, ничто не гарантирует того, что вы не сможете помешать ссылкам на целевой объект в процессе очистки.Будьте внимательны при чтении длинного API Note!Если вы в конце концов сделаете ссылку на объект, весь механизм будет молча сломаться, в отличие от финализации, которая всегда пытается хромать.Наконец, хотя потоком завершения управляет JVM, создание и удержание потоков Cleaner - ваша собственная ответственность.

Ответы [ 4 ]

0 голосов
/ 21 октября 2018

Не используйте ни.

Попытка восстановления после утечек ресурсов с помощью Cleaner представляет почти столько же проблем, сколько finalize, худшая из которых, как упомянул Хольгер, является преждевременным завершением(что является проблемой не только для finalize, но и для всех типов мягких / слабых / фантомных ссылок).Даже если вы приложите все усилия для правильной реализации финализации (и, опять же, я имею в виду любую систему, которая использует мягкую / слабую / фантомную ссылку), вы никогда не сможете гарантировать, что утечка ресурсов не приведет к исчерпанию ресурсов.Неизбежным фактом является то, что сборщик мусора не знает о ваших ресурсах.

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

Утечки ресурсов должны быть исправлены, а не устранены.

С этой целью я предлагаювзглянем на полученный из Netty almson-refcount .Он предлагает эффективный детектор утечек , основанный на слабых ссылках, и дополнительное средство подсчета ссылок, которое является более гибким, чем обычное автозаполнение.Что делает его детектор утечки отличным, так это то, что он предлагает разные уровни отслеживания (с разным объемом служебной информации), и вы можете использовать его для захвата следов стека, где размещены и использованы ваши утечек.

0 голосов
/ 18 октября 2018

Java 9 Cleaner очень похож на традиционную финализацию (реализованную в OpenJDK), и почти все, что можно сказать о финализации (хорошо или плохо), можно сказать и о Cleaner.Оба полагаются на сборщик мусора для размещения Reference объектов на ReferenceQueue и используют отдельный поток для запуска методов очистки.

Три основных различия заключаются в том, что Cleaner использует PhantomReference вместо того, что по сути является WeakReference, использует отдельный поток для каждого очистителя с настраиваемой ThreadFactory и позволяет очищать ссылки PhantomReferences (то есть отменять)вручную и никогда не ставится в очередь.

Это обеспечивает преимущества в производительности, когда интенсивно используется очиститель / финализация.(К сожалению, у меня нет эталонных тестов, чтобы сказать, сколько преимуществ.) Однако интенсивное использование финализации не является нормальным.

Для обычных вещей, для которых используется finalize - т.е.Механизм очистки в крайнем случае для собственных ресурсов, реализованный с небольшими конечными объектами, которые содержат минимально необходимое состояние, обеспечивают AutoCloseable и не выделяются миллионы в секунду - между этими двумя подходами нет никакой практической разницы, кромеразличия в использовании (в некоторых аспектах finalize проще в реализации, в других Cleaner помогает избежать ошибок).Уборщик не предоставляет каких-либо дополнительных гарантий или действий (таких как гарантия того, что очистители будут работать до завершения процесса - что по сути невозможно гарантировать в любом случае).

Однако finalize былустарела. Так вот, наверное.Что-то вроде движения члена.Возможно, разработчики JDK задумались, "почему JDK должен предоставить собственный механизм, который можно легко реализовать в виде библиотеки""n00bs. N00bs везде. N00bs, прекратите использовать finalize, мы вас так ненавидим".Это хороший момент - и все же, я не могу представить, чтобы finalize действительно исчезал.Вероятно, это всегда будет работать, поэтому я не собираюсь останавливаться на этом.

Хорошую статью, в которой рассказывается о финализации и обрисовывается, как работает альтернативная финализация, можно найти здесь: Как обращаться с памятью финализации Java-Retention Issues В общих чертах он описывает, как работает Cleaner.

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

Netty's ResourceLeakDetector намного круче, чем Cleaner.

0 голосов
/ 19 октября 2018

Вы не должны заменять все finalize() методы на Cleaner.Тот факт, что устаревание метода finalize() и введение (a public) Cleaner произошло в одной и той же версии Java, указывает только на то, что произошла общая работа над темой, а не на то, что она должна быть заменойдругого.

Другая связанная работа этой версии Java - удаление правила, согласно которому PhantomReference не очищается автоматически (да, до Java 9 использование PhantomReference вместо finalize() все еще).потребовалось два цикла GC для восстановления объекта) и введение Reference.reachabilityFence(…).

Первая альтернатива finalize() - вообще не иметь зависимой от сборки мусора операции.Хорошо, когда вы говорите, что у вас их немного, но я видел полностью устаревшие finalize() методы в дикой природе.Проблема в том, что finalize() выглядит как обычный protected метод, а на некоторых интернет-страницах все еще распространен цепкий миф о том, что finalize() был своего рода деструктором.Маркировка устарела позволяет сигнализировать разработчику, что это не так, без нарушения совместимости.Использование механизма, требующего явной регистрации, помогает понять, что это не обычный программный поток.И это не повредит, когда выглядит более сложным, чем переопределение одного метода.

Если ваш класс инкапсулирует ресурс без кучи, документация гласит:

Классы, экземпляры которых содержат ресурсы, не относящиеся к куче, должны предоставлять метод, позволяющий явное освобождение этих ресурсов, и они также должны реализовывать AutoCloseable , если это необходимо.

(такэто предпочтительное решение)

Очиститель и PhantomReference предоставляют более гибкие и эффективные способы освобождения ресурсов, когда объект становится недоступным.

Поэтому, когда вам действительно необходимо взаимодействие с сборщиком мусора, даже в этом кратком комментарии к документации указаны две альтернативы, так как PhantomReference здесь не упоминается как скрытый от разработчика бэкэнд Cleaner здесь;использование PhantomReference напрямую является альтернативой Cleaner, которое может быть еще более сложным в использовании, но также обеспечивает еще больший контроль над временем и потоками, включая возможность очистки в том же потоке, который использовал ресурс.(Сравните с WeakHashMap, который имеет такую ​​очистку, избегая затрат на многопоточные конструкции).Это также позволяет работать с исключениями, возникающими во время очистки, лучше, чем тихое их проглатывание.

Но даже Cleaner решает больше проблем, о которых вы знаете.

Существенная проблема,время регистрации.

  • Объект класса с нетривиальным методом finalize() регистрируется при выполнении конструктора Object().На данный момент объект еще не был инициализирован.Если ваша инициализация завершена с исключением, метод finalize() все равно будет вызван.Может быть заманчиво решить это с помощью данных объекта, например, установив флаг initialized на true, но вы можете сказать это только для ваших собственных данных экземпляра, но не для данных подкласса, который еще не был инициализированкогда ваш конструктор вернется.

    Для регистрации очистителя требуется полностью построенный Runnable, содержащий все необходимые данные для очистки, без ссылки на строящийся объект.Вы можете даже отложить регистрацию, когда распределение ресурсов не произошло в конструкторе (представьте себе несвязанный экземпляр Socket или Frame, который не атомарно связан с дисплеем)

  • A 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?

0 голосов
/ 18 октября 2018

Как отметил Эллиотт в комментариях , продвигаясь вперед с Java9 +, Object.finalize устарел, и поэтому имеет смысл реализовывать методы с использованием Cleaner.Кроме того, из примечаний к выпуску:

Метод java.lang.Object.finalize устарел.Механизм финализации по своей сути проблематичен и может привести к проблемам с производительностью, тупикам и зависаниям.java.lang.ref.Cleaner и java.lang.ref.PhantomReference предоставляют более гибкие и эффективные способы освобождения ресурсов, когда объект становится недоступным.

Подробности в базе данных ошибок - JDK-8165641

...