Зачем вам реализовывать finalize ()?

371

Я перечитал множество вопросов о новичке в Java finalize()и обнаружил, что это немного сбивает с толку, что никто не дал понять, что finalize () - это ненадежный способ очистки ресурсов. Я видел, как кто-то комментирует, что он использует его для очистки соединений, что действительно страшно, поскольку единственный способ приблизиться к гарантии закрытия соединения - это реализовать try (catch) в конце концов.

Я не учился в CS, но я профессионально программирую на Java уже почти десять лет, и я никогда не видел, чтобы кто-нибудь реализовывал finalize()в производственной системе. Это по-прежнему не означает, что он не имеет смысла или что люди, с которыми я работал, делали это правильно.

Поэтому мой вопрос: какие варианты использования существуют для реализации, finalize()которые не могут быть обработаны более надежно с помощью другого процесса или синтаксиса в языке?

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

Спенсер Кормос
источник
HI метод finalize () очень хорошо объяснен здесь howtodoinjava.com/2012/10/31/…
Самир Кази
2
Большая часть кода приложения и библиотеки никогда не будет использоваться finalize(). Однако код библиотеки платформы , например SocketInputStream, который управляет собственными ресурсами от имени вызывающей стороны, делает это, чтобы минимизировать риск утечек ресурсов (или использует эквивалентные механизмы, такие как PhantomReference, которые были добавлены позже). Таким образом, экосистема нуждается в них хотя 99,9999% разработчиков никогда не напишут.
Брайан Гетц

Ответы:

230

Вы можете использовать его в качестве backstop для объекта, содержащего внешний ресурс (сокет, файл и т. Д.). Реализуйте close()метод и документ, который нужно вызвать.

Реализуйте, finalize()чтобы сделать close()обработку, если вы обнаружите, что это не было сделано. Может быть с чем-то сброшено, чтобы stderrуказать, что вы убираете после глючного звонящего.

Это обеспечивает дополнительную безопасность в исключительной / глючной ситуации. Не каждый абонент будет делать правильные try {} finally {}вещи каждый раз. К сожалению, но верно в большинстве сред.

Я согласен, что это редко нужно. И, как отмечают комментаторы, это идет с накладными расходами GC. Используйте только в том случае, если вам нужна безопасность «ремня и подтяжек» в длительном приложении.

Я вижу, что с Java 9 Object.finalize()устарела! Они указывают на нас java.lang.ref.Cleanerи в java.lang.ref.PhantomReferenceкачестве альтернативы.

Джон М
источник
44
Просто убедитесь, что finalize () никогда не генерирует исключение, иначе сборщик мусора не продолжит очистку этого объекта, и вы получите утечку памяти.
Скаффман
17
Finalize не гарантированно будет вызываться, поэтому не рассчитывайте на освобождение ресурса.
flicken
19
skaffman - я не верю этому (кроме некоторой ошибочной реализации JVM). Из javadoc Object.finalize (): Если метод finalize создает неперехваченное исключение, оно игнорируется, и завершение этого объекта завершается.
Джон М
4
Ваше предложение может скрывать ошибки людей (не вызывая близко) в течение длительного времени. Я бы не рекомендовал это делать.
Колерм
64
Я на самом деле использовал finalize именно по этой причине. Я унаследовал код, и он был очень глючным и имел тенденцию оставлять открытыми соединения с базой данных. Мы изменили соединения, чтобы они содержали данные о том, когда и где они были созданы, а затем осуществили финализацию для регистрации этой информации, если соединение не было закрыто должным образом. Оказалось, что это очень помогает отследить незамкнутые соединения.
brainimus
173

finalize()является подсказкой для JVM, что было бы неплохо выполнить ваш код в неуказанное время. Это хорошо, когда вы хотите, чтобы код загадочным образом не работал.

Делать что-либо существенное в финализаторах (в основном все, кроме логирования) также хорошо в трех ситуациях:

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

Если вы считаете, что вам нужен finalize (), иногда вам действительно нужна фантомная ссылка (которая в приведенном примере может содержать жесткую ссылку на соединение, используемое его референтом, и закрывать его после того, как фантомная ссылка была помещена в очередь). У него также есть свойство, которое он может таинственно никогда не запускать, но, по крайней мере, он не может вызывать методы или воскрешать завершенные объекты. Так что это как раз подходит для ситуаций, когда вам абсолютно не нужно точно закрывать это соединение, но вы бы очень этого хотели, и клиенты вашего класса не могут или не будут звонить близко друг к другу (что на самом деле достаточно справедливо - что' смысл ли вообще иметь сборщик мусора, если вы разрабатываете интерфейсы, которые требуют определенных действий перед сборкой ? Это просто возвращает нас во времена malloc / free.)

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

Отказ от ответственности: я работал над реализацией JVM в прошлом. Я ненавижу финализаторы.

Стив Джессоп
источник
76
Голосование за крайний сарказм справедливости.
Восприятие
32
Это не отвечает на вопрос; он просто говорит о всех недостатках finalize()без объяснения причин, по которым кто-то действительно захочет его использовать.
Механическая улитка
1
@supercat: фантомные ссылки (в этом отношении все ссылки) были введены в Java 2
Стив Джессоп
1
@supercat: извините, да, под "ссылками" я имел в виду java.lang.ref.Referenceи его подклассы. В Java 1 действительно были переменные :-) И да, у нее тоже были финализаторы.
Стив Джессоп
2
@SteveJessop: Если конструктор базового класса просит другие сущности изменить свое состояние от имени создаваемого объекта (очень распространенный шаблон), но инициализатор поля производного класса выдает исключение, частично созданный объект должен немедленно очистить после себя (т. е. уведомить тех внешних объектов, что их услуги больше не требуются), но ни Java, ни .NET не предлагают даже шаблонов, даже удаленно чистых, с помощью которых это может произойти. В самом деле, они, кажется, стараются изо всех сил помешать ссылаться на то, что нуждается в очистке для достижения кода очистки.
суперкат
57

Простое правило: никогда не используйте финализаторы.

Уже одного факта, что объект имеет финализатор (независимо от того, какой код он выполняет), достаточно, чтобы вызвать значительные издержки на сборку мусора.

Из статьи Брайана Гетца:

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

Том
источник
46

Единственный раз, когда я использовал finalize в производственном коде, было выполнение проверки того, что ресурсы данного объекта были очищены, и если нет, то записывать очень громкое сообщение. На самом деле он не пытался сделать это сам, он просто много кричал, если это не было сделано должным образом. Оказалось весьма полезным.

skaffman
источник
34

Я профессионально занимаюсь Java с 1998 года и никогда не реализовывал finalize(). Ни разу.

Пол Томблин
источник
Как мне тогда уничтожить объект публичного класса? Это вызывает проблему в АПД. По сути, предполагается перезагрузить страницу (получать свежие данные из API), но она берет старый объект и показывает результаты
InfantPro'Aravind '
2
@ InfantPro'Aravind 'вызов finalize не удаляет объект. Это реализуемый вами метод, который JVM может выбрать для вызова, если и когда мусор собирает ваш объект.
Пол Томблин
@PaulTomblin, ооо, спасибо за эту деталь. Любое предложение обновить страницу на ADF?
InfantPro'Aravind '
@ InfantPro'Aravind 'Понятия не имею, никогда не использовал ADF.
Пол Томблин
28

Принятый ответ хорош, я просто хотел добавить, что теперь есть способ завершить работу, не используя ее вообще.

Посмотрите на «Справочные» классы. Слабая ссылка, фантомная ссылка и мягкая ссылка.

Вы можете использовать их, чтобы сохранить ссылку на все ваши объекты, но эта ссылка ALONE не остановит GC. Неплохо то, что вы можете вызывать метод, когда он будет удален, и этот метод может быть гарантированно вызван.

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

public void finalize() {
  ref1 = null;
  ref2 = null;
  othercrap = null;
}

Это признак того, что кто-то не знал, что делал. Такая «очистка» практически никогда не нужна. Когда класс GC'd, это делается автоматически.

Если вы найдете такой код в финализации, это гарантирует, что человек, который написал его, был сбит с толку.

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

Это никогда не должно использоваться, чтобы убрать вещи "Быстрее".

Билл К
источник
3
Я думаю, что фантомные ссылки - лучший способ отслеживать, какие объекты освобождаются.
Билл Мичелл
2
голосование за предоставление лучшего объяснения "слабой ссылки", которую я когда-либо слышал.
sscarduzio
Мне жаль, что это заняло у меня так много лет, чтобы прочитать это, но это действительно хорошее дополнение к ответам.
Спенсер Кормос
27

Я не уверен, что вы можете сделать из этого, но ...

itsadok@laptop ~/jdk1.6.0_02/src/
$ find . -name "*.java" | xargs grep "void finalize()" | wc -l
41

Таким образом, я думаю, что Солнце обнаружило несколько случаев, когда (они думают) это следует использовать.

itsadok
источник
12
Я думаю, что это также вопрос "Всегда ли разработчик Sun будет прав"?
CodeReaper
13
Но сколько из этих применений просто реализуют или тестируют поддержку, finalizeа не фактически используют ее?
Механическая улитка
Я использую Windows. Как бы вы сделали то же самое в Windows?
gparyani
2
@damryfbfnetsi: Попробуйте установить ack ( beyondgrep.com ) с помощью chocolatey.org/packages/ack . Или используйте простую функцию поиска в файлах в $FAVORITE_IDE.
Брайан Клайн
21
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!");  
    }
}

====================================

результат:

This is finalize

MyObject still alive!

=====================================

Таким образом, вы можете сделать недоступный экземпляр доступным в методе finalize.

Kiki
источник
21
По какой-то причине этот код заставляет меня думать о фильмах про зомби. Вы собираете свой объект, а затем он снова встает ...
Steveha
выше хорошие коды. Мне было интересно, как сделать объект снова доступным только сейчас.
lwpro2
15
нет, выше плохие коды. это пример того, почему финализация плохая.
Тони
Помните, что вызов System.gc();не является гарантией того, что сборщик мусора действительно будет работать. Это полное усмотрение GC по очистке кучи (может быть, все еще достаточно свободной кучи, может быть, не фрагментированной, ...). Так что это всего лишь скромная просьба программиста: «Пожалуйста, вы могли бы выслушать меня и запустить?» а не команда «беги сейчас, как я говорю!». Извините за использование языковых слов, но это говорит о том, как именно работает GC JVM.
Роланд
11

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

Я программирую на Java с версии 1.0 alpha 3 (1995), и мне еще предстоит переопределить финализировать что-либо ...

TofuBeer
источник
6

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

Билл Ящерица
источник
5

Чтобы выделить точку в ответах выше: финализаторы будут выполняться в одиночном потоке GC. Я слышал о крупной демонстрации Sun, где разработчики добавили небольшой сон некоторым финализаторам и намеренно поставили на колени необычную 3D-демонстрацию.

Лучше всего избегать, с возможным исключением диагностической диагностики.

У Eckel's Thinking in Java есть хороший раздел на эту тему.

Майкл Пасха
источник
4

Хм, я однажды использовал его для очистки объектов, которые не были возвращены в существующий пул.

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

Hearen
источник
4

Будьте осторожны с тем, что вы делаете в finalize(). Особенно, если вы используете его для таких вещей, как вызов close () для обеспечения очистки ресурсов. Мы столкнулись с несколькими ситуациями, когда у нас были библиотеки JNI, связанные с работающим java-кодом, и при любых обстоятельствах, когда мы использовали finalize () для вызова методов JNI, мы могли получить очень плохое повреждение кучи Java. Повреждение не было вызвано самим исходным кодом JNI, все следы памяти были в порядке в собственных библиотеках. Это был просто тот факт, что мы вообще вызывали JNI-методы из finalize ().

Это было с JDK 1.5, который все еще широко используется.

Мы не узнаем, что что-то пошло не так, пока намного позже, но в итоге виновником всегда был метод finalize (), использующий вызовы JNI.

Стивен М. Черри
источник
3

При написании кода, который будет использоваться другими разработчиками, для освобождения ресурсов требуется некоторый метод «очистки». Иногда эти другие разработчики забывают вызвать ваш метод очистки (или закрыть, или уничтожить, или как угодно). Чтобы избежать возможных утечек ресурсов, вы можете проверить в методе finalize, чтобы убедиться, что метод был вызван, и если это не так, вы можете вызвать его самостоятельно.

Многие драйверы баз данных делают это в своих реализациях Statement и Connection, чтобы обеспечить небольшую безопасность от разработчиков, которые забывают обращаться к ним близко.

Джон Мигер
источник
2

Редактировать: Хорошо, это действительно не работает. Я реализовал это и подумал, что если это иногда не удается, это нормально для меня, но он даже не вызывал метод 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);
}
user265243
источник
Я думаю, что кеш (того типа, который вы бы записали на диск) - хороший пример. И даже если финализация не называется, нет пота.
Foo
2

Это может быть удобно, чтобы удалить вещи, которые были добавлены в глобальное / статическое место (по необходимости), и которые должны быть удалены при удалении объекта. Например:

    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);
        }
    }
Александр Малфей
источник
Это требует дополнительных усилий, так как когда у слушателя есть сильная ссылка на thisэкземпляр, переданный ему в конструкторе, этот экземпляр никогда не станет недоступным, следовательно, finalize()он никогда не очистится в любом случае. Если, с другой стороны, слушатель имеет слабую ссылку на этот объект, как следует из названия, эта конструкция рискует быть очищенной в те моменты, когда этого не следует делать. Жизненные циклы объектов не являются правильным инструментом для реализации семантики приложения.
Хольгер
0

iirc - вы можете использовать метод finalize как средство реализации механизма пулирования для дорогих ресурсов, чтобы они тоже не получали GC.

JGFMK
источник
0

Как примечание стороны:

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

Источник: finalize () устарела на Java-9

Судип Бхандари
источник
0

Ресурсы (File, Socket, Stream и т. Д.) Должны быть закрыты, как только мы закончим с ними. У них обычно есть close()метод, который мы обычно вызываем в finallyразделе try-catchоператоров. Иногда finalize()могут также использоваться немногие разработчики, но IMO не является подходящим способом, поскольку нет гарантии, что финализация будет вызываться всегда.

В 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 в наших собственных классах и использовать его аналогичным образом. ИМО кажется более аккуратным и простым для понимания.

akhil_mittal
источник
0

Лично я почти никогда не использовал, 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. Я также подумал о том, чтобы остановить любые темы, но это открыло новую банку с червями.

Supuhstar
источник
4
Вызов finalize()на ваших CompleteObjectслучаях , как вы делаете в коде выше означает , что она может вызываться дважды, как JVM все еще может вызывать finalize()только эти объекты фактически стали недостижимыми, что, в связи с логикой завершения, может быть , прежде чем в finalize()методе вашей специальной коллекции вызывается или даже одновременно в одно и то же время. Поскольку я не вижу каких-либо мер для обеспечения безопасности потоков в вашем методе, вы, похоже, не знаете об этом…
Хольгер,
0

Список принятых ответов, что закрытие ресурса во время финализации может быть сделано.

Однако этот ответ показывает, что, по крайней мере, в java8 с JIT-компилятором вы сталкиваетесь с неожиданными проблемами, когда иногда финализатор вызывается даже до того, как вы закончите чтение из потока, поддерживаемого вашим объектом.

Так что даже в такой ситуации не рекомендуется использовать финализацию .

Joeblade
источник
Это будет работать, если операция сделана поточно-безопасной, что является обязательным условием, поскольку финализаторы могут вызываться произвольными потоками. Поскольку правильно реализованная безопасность потоков подразумевает, что не только операция закрытия, но и операции, использующие ресурс, должны синхронизироваться с одним и тем же объектом или блокировкой, между использованием и операцией закрытия будет возникать случайная связь, которая также предотвращает слишком раннее финализации. Но, конечно, по высокой цене за все использование…
Хольгер