Зачем вам реализовывать finalize()?
Я прочитал много вопросов о новичке в Java по finalize() и обнаружил, что это немного сбивает с толку, что никто не дал понять, что finalize() - ненадежный способ очистки ресурсов. Я видел, как кто-то комментирует, что они используют его для очистки соединений, что действительно страшно, поскольку единственный способ приблизиться к гарантии закрытия соединения - это реализовать try (catch) наконец.
Я не учился в CS, но профессионально программирую на Java уже почти десять лет, и я никогда не видел, чтобы кто-нибудь реализовывал finalize() в производственной системе. Это по-прежнему не означает, что у него нет использования, или что люди, с которыми я работал, делали это правильно.
Поэтому мой вопрос: какие варианты использования существуют для реализации finalize(), которая не может быть обработана более надежно с помощью другого процесса или синтаксиса в языке?
Пожалуйста, предоставьте конкретные сценарии или ваш опыт, просто повторения учебника по Java или окончательного использования по назначению недостаточно, и это не является целью этого вопроса.
21 ответ
Вы можете использовать его в качестве backstop для объекта, содержащего внешний ресурс (сокет, файл и т. Д.). Реализовать close()
метод и документ, который нужно вызвать.
Воплощать в жизнь finalize()
сделать close()
обработка, если вы обнаружите, что это не было сделано. Может быть, с чем-то, сброшенным в stderr, чтобы указать, что вы убираете после вызывающего ошибки глагола.
Это обеспечивает дополнительную безопасность в исключительной / глючной ситуации. Не каждый звонящий собирается поступить правильно try {} finally {}
вещи каждый раз. К сожалению, но верно в большинстве сред.
Я согласен, что это редко нужно. И, как отмечают комментаторы, это идет с накладными расходами GC. Используйте только в том случае, если вам нужна безопасность "пояса и подтяжек" в долгосрочном приложении.
Я вижу, что с Java 9, Object.finalize()
устарела! Они указывают нам на java.lang.ref.Cleaner
а также java.lang.ref.PhantomReference
в качестве альтернативы.
finalize() - это подсказка JVM, что было бы неплохо выполнить ваш код в неуказанное время. Это хорошо, когда вы хотите, чтобы код загадочным образом не работал.
Делать что-либо существенное в финализаторах (в основном все, кроме логирования) также хорошо в трех ситуациях:
- вы хотите, чтобы другие завершенные объекты все еще находились в состоянии, которое остальная часть вашей программы считает действительным.
- Вы хотите добавить множество проверочных кодов ко всем методам всех ваших классов, которые имеют финализатор, чтобы убедиться, что они работают правильно после финализации.
- Вы хотите случайно воскресить завершенные объекты и потратить много времени, пытаясь выяснить, почему они не работают и / или почему они не завершаются, когда они в конечном итоге освобождаются.
Если вы считаете, что вам нужен finalize(), иногда вам действительно нужна фантомная ссылка (которая в приведенном примере может содержать жесткую ссылку на соединение, используемое его референтом, и закрывать его после того, как фантомная ссылка была помещена в очередь). У него также есть свойство, которое он может таинственно никогда не запускать, но, по крайней мере, он не может вызывать методы или воскрешать завершенные объекты. Так что это как раз подходит для ситуаций, когда вам абсолютно не нужно точно закрывать это соединение, но вы бы очень этого хотели, и клиенты вашего класса не могут или не будут звонить закрывать себя (что на самом деле достаточно справедливо - какой смысл вообще иметь сборщик мусора, если вы разрабатываете интерфейсы, требующие выполнения определенных действий перед сборкой? Это просто возвращает нас во времена malloc/free.)
В других случаях вам нужен ресурс, который вы считаете более эффективным. Например, почему вам нужно закрыть это соединение? В конечном итоге он должен основываться на каком-либо вводе-выводе, предоставляемом системой (сокет, файл и т. Д.), Так почему же вы не можете полагаться на то, что система закроет его для вас, когда выделен самый низкий уровень ресурсов? Если серверу на другом конце абсолютно необходимо, чтобы вы аккуратно закрыли соединение, а не просто уронили сокет, то что произойдет, если кто-то споткнется о кабель питания машины, на которой работает ваш код, или промежуточная сеть погаснет?
Отказ от ответственности: я работал над реализацией JVM в прошлом. Я ненавижу финализаторы.
Простое правило: никогда не используйте финализаторы. Уже одного факта, что объект имеет финализатор (независимо от того, какой код он выполняет), достаточно, чтобы вызвать значительные издержки на сборку мусора.
Из статьи Брайана Гетца:
Объекты с финализаторами (те, которые имеют нетривиальный метод finalize()) имеют значительные накладные расходы по сравнению с объектами без финализаторов и должны использоваться экономно. Завершаемые объекты медленнее распределяются и медленнее собираются. Во время выделения JVM должна зарегистрировать любые объекты финализируемости с помощью сборщика мусора, и (по крайней мере в реализации HotSpot JVM) объекты финализируемого объекта должны следовать более медленному пути выделения, чем большинство других объектов. Точно так же финализуемые объекты собирать медленнее. Требуется по крайней мере два цикла сборки мусора (в лучшем случае), прежде чем финализуемый объект может быть возвращен, и сборщик мусора должен выполнить дополнительную работу, чтобы вызвать финализатор. В результате вы тратите больше времени на выделение и сбор объектов и повышаете нагрузку на сборщик мусора, поскольку память, используемая недоступными для завершения объектами, сохраняется дольше. Объедините это с тем фактом, что финализаторы не гарантированно будут работать в любой предсказуемый период времени или даже вообще, и вы увидите, что существует относительно немного ситуаций, для которых финализация является подходящим инструментом для использования.
Единственный раз, когда я использовал finalize в производственном коде, была реализация проверки того, что ресурсы данного объекта были очищены, а если нет, то запись очень громкого сообщения. На самом деле он не пытался сделать это сам, он просто много кричал, если это не было сделано должным образом. Оказалось, довольно полезно.
Я профессионально занимаюсь Java с 1998 года и никогда не реализовывал finalize(). Ни разу.
Я использовал finalize один раз, чтобы понять, какие объекты были освобождены. Вы можете играть в некоторые аккуратные игры со статикой, подсчетом ссылок и тому подобным - но это было только для анализа.
Принятый ответ хорош, я просто хотел добавить, что теперь есть способ завершить работу, не используя ее вообще.
Посмотрите на "Справочные" классы. Слабая ссылка и т. Д.
Вы можете использовать их, чтобы сохранить ссылку на все ваши объекты, но эта ссылка ALONE не остановит GC. Неплохо то, что вы можете вызывать метод, когда он будет удален, и этот метод может быть гарантированно вызван.
Еще одна вещь, на которую стоит обратить внимание. Каждый раз, когда вы видите что-либо подобное в коде (не только в финализации, но именно там вы, скорее всего, это увидите):
public void finalize() {
ref1 = null;
ref2 = null;
othercrap = null;
}
Это признак того, что кто-то не знал, что делал. Такая "очистка" практически никогда не нужна. Когда класс GC'd, это делается автоматически.
Если вы найдете такой код в финализации, это гарантирует, что человек, который написал его, был сбит с толку.
Если это где-то в другом месте, возможно, что код является допустимым исправлением для плохой модели (класс остается в течение долгого времени, и по некоторым причинам вещи, на которые он ссылается, должны были быть вручную освобождены до того, как объект будет собран GC). Обычно это потому, что кто-то забыл удалить слушателя или что-то в этом роде и не может понять, почему его объект не является GC, поэтому он просто удаляет вещи, на которые он ссылается, пожимает плечами и уходит.
Это никогда не должно использоваться, чтобы убрать вещи "Быстрее".
Я не уверен, что вы можете сделать из этого, но...
itsadok@laptop ~/jdk1.6.0_02/src/
$ find . -name "*.java" | xargs grep "void finalize()" | wc -l
41
Таким образом, я думаю, что Солнце обнаружило несколько случаев, когда (они думают) это следует использовать.
class MyObject {
Test main;
public MyObject(Test t) {
main = t;
}
protected void finalize() {
main.ref = this; // let instance become reachable again
System.out.println("This is finalize"); //test finalize run only once
}
}
class Test {
MyObject ref;
public static void main(String[] args) {
Test test = new Test();
test.ref = new MyObject(test);
test.ref = null; //MyObject become unreachable,finalize will be invoked
System.gc();
if (test.ref != null) System.out.println("MyObject still alive!");
}
}
====================================
результат:
Это доработать
MyObject еще жив!
=====================================
Таким образом, вы можете сделать недоступный экземпляр доступным в методе finalize.
finalize может быть полезен для обнаружения утечек ресурсов. Если ресурс должен быть закрыт, но это не так, напишите, что он не был закрыт для файла журнала, и закройте его. Таким образом вы устраните утечку ресурсов и дадите себе возможность узнать, что это произошло, чтобы вы могли это исправить.
Я программирую на Java с версии 1.0 alpha 3 (1995), и мне еще предстоит переопределить finalize для чего-либо...
Вы не должны зависеть от finalize(), чтобы очистить ваши ресурсы для вас. finalize() не будет работать, пока класс не будет собран мусором, если тогда. Гораздо лучше явно освобождать ресурсы, когда вы их используете.
Будьте осторожны с тем, что вы делаете в финализации (). Особенно, если вы используете его для таких вещей, как вызов close() для обеспечения очистки ресурсов. Мы столкнулись с несколькими ситуациями, когда у нас были библиотеки JNI, связанные с работающим java-кодом, и при любых обстоятельствах, когда мы использовали finalize() для вызова методов JNI, мы могли получить очень плохое повреждение кучи java. Повреждение не было вызвано самим исходным кодом JNI, все следы памяти были в порядке в собственных библиотеках. Это был просто тот факт, что мы вообще вызывали методы JNI из finalize().
Это было с JDK 1.5, который все еще широко используется.
Мы не узнаем, что что-то пошло не так, пока намного позже, но в итоге виновником всегда был метод finalize(), использующий вызовы JNI.
Чтобы выделить точку в ответах выше: финализаторы будут выполняться в одиночном потоке GC. Я слышал о крупной демонстрации Sun, где разработчики добавили небольшой сон некоторым финализаторам и намеренно поставили на колени необычную 3D-демонстрацию.
Лучше всего избегать, с возможным исключением диагностической диагностики.
У Eckel's Thinking in Java есть хороший раздел на эту тему.
Хм, я однажды использовал его для очистки объектов, которые не были возвращены в существующий пул. Их много раздали, поэтому невозможно было сказать, когда их можно было безопасно вернуть в бассейн. Проблема заключалась в том, что при сборке мусора вводился огромный штраф, который намного превышал любую экономию от объединения объектов. Это было в производстве около месяца, прежде чем я разорвал весь пул, сделал все динамично и покончил с этим.
При написании кода, который будет использоваться другими разработчиками, для освобождения ресурсов требуется некоторый метод "очистки". Иногда эти другие разработчики забывают вызвать ваш метод очистки (или закрыть, или уничтожить, или что-то еще). Чтобы избежать возможных утечек ресурсов, вы можете проверить в методе finalize, чтобы убедиться, что метод был вызван, и если это не так, вы можете вызвать его самостоятельно.
Многие драйверы баз данных делают это в своих реализациях Statement и Connection, чтобы обеспечить небольшую безопасность от разработчиков, которые забывают обращаться к ним близко.
Редактировать: Хорошо, это действительно не работает. Я реализовал это и подумал, что если это не удается, иногда это нормально для меня, но он даже не вызывал метод finalize один раз.
Я не профессиональный программист, но в моей программе есть случай, который я считаю примером удачного использования finalize(), то есть кеш, который записывает свое содержимое на диск до его уничтожения. Поскольку нет необходимости, чтобы он выполнялся каждый раз при уничтожении, он только ускоряет мою программу, я надеюсь, что я не сделал это неправильно.
@Override
public void finalize()
{
try {saveCache();} catch (Exception e) {e.printStackTrace();}
}
public void saveCache() throws FileNotFoundException, IOException
{
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("temp/cache.tmp"));
out.writeObject(cache);
}
Это может быть удобно для удаления вещей, которые были добавлены в глобальное / статическое место (по необходимости), и которые должны быть удалены при удалении объекта. Например:
private void addGlobalClickListener () { weakAwtEventListener = new WeakAWTEventListener (это); Toolkit.getDefaultToolkit(). AddAWTEventListener(weakAwtEventListener, AWTEvent.MOUSE_EVENT_MASK); } @Override Защищенный void finalize() выбрасывает Throwable { super.finalize(); if(weakAwtEventListener!= null) {. Toolkit.getDefaultToolkit () removeAWTEventListener(weakAwtEventListener); } }
Список принятых ответов, что закрытие ресурса во время финализации может быть сделано.
Однако этот ответ показывает, что, по крайней мере, в java8 с JIT-компилятором вы сталкиваетесь с неожиданными проблемами, когда иногда финализатор вызывается даже до того, как вы закончите чтение из потока, поддерживаемого вашим объектом.
Так что даже в такой ситуации не рекомендуется использовать финализацию.
iirc - вы можете использовать метод finalize как средство реализации механизма пулирования для дорогих ресурсов, чтобы они тоже не получали GC.
Лично я почти никогда не пользуюсь finalize()
за исключением одного редкого обстоятельства: я создал собственную коллекцию универсального типа и написал собственную finalize()
метод, который делает следующее:
public void finalize() throws Throwable {
super.finalize();
if (destructiveFinalize) {
T item;
for (int i = 0, l = length(); i < l; i++) {
item = get(i);
if (item == null) {
continue;
}
if (item instanceof Window) {
((Window) get(i)).dispose();
}
if (item instanceof CompleteObject) {
((CompleteObject) get(i)).finalize();
}
set(i, null);
}
}
}
( CompleteObject
это интерфейс, который я сделал, который позволяет вам указать, что вы реализовали редко реализуемый Object
методы, такие как #finalize()
, #hashCode()
, а также #clone()
)
Итак, используя сестру #setDestructivelyFinalizes(boolean)
метод, программа, использующая мою коллекцию, может (помочь) гарантировать, что уничтожение ссылки на эту коллекцию также уничтожает ссылки на ее содержимое и удаляет все окна, которые могут непреднамеренно поддерживать JVM. Я также подумал о том, чтобы остановить любые темы, но это открыло новую банку с червями.
Как примечание стороны:
Объект, который переопределяет finalize(), обрабатывается сборщиком мусора специально. Обычно объект немедленно уничтожается во время цикла сбора после того, как объект больше не находится в области видимости. Однако вместо этого объекты финализуемого объекта перемещаются в очередь, где отдельные потоки финализации истощают очередь и запускают метод finalize() для каждого объекта. Как только метод finalize() завершится, объект будет наконец готов для сборки мусора в следующем цикле.
Ресурсы (File, Socket, Stream и т. Д.) Должны быть закрыты после того, как мы закончили с их использованием. У них вообще есть close()
метод, который мы обычно называем в finally
раздел try-catch
заявления. Иногда finalize()
также могут использоваться несколькими разработчиками, но IMO не подходит, так как нет гарантии, что finalize будет вызываться всегда.
В Java 7 у нас есть оператор try-with-resources, который можно использовать так:
try (BufferedReader br = new BufferedReader(new FileReader(path))) {
// Processing and other logic here.
} catch (Exception e) {
// log exception
} finally {
// Just in case we need to do some stuff here.
}
В приведенном выше примере try-with-resource автоматически закроет ресурс BufferedReader
вызывая close()
метод. Если мы хотим, мы также можем реализовать Closeable в наших собственных классах и использовать его аналогичным образом. ИМО кажется более аккуратным и простым для понимания.