Зачем вам когда-либо реализовывать finalize()?

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

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

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

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

Ответ 1

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

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

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

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

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

Ответ 2

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

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

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

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

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

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

Ответ 3

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

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

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

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

Ответ 4

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

Ответ 5

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

Ответ 6

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

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

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

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

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

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

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

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

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

Ответ 7

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

[email protected] ~/jdk1.6.0_02/src/
$ find . -name "*.java" | xargs grep "void finalize()" | wc -l
41

Итак, я думаю, что Sun обнаружил некоторые случаи, когда (они думают) он должен использоваться.

Ответ 8

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.

Ответ 9

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

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

Ответ 10

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

Ответ 11

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

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

Eckel Мышление в Java хороший раздел по этому вопросу.

Ответ 12

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

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

Ответ 13

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

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

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

Ответ 14

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

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

Ответ 15

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

Ответ 16

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

    private void addGlobalClickListener() {
        weakAwtEventListener = new WeakAWTEventListener(this);

        Toolkit.getDefaultToolkit().addAWTEventListener(weakAwtEventListener, AWTEvent.MOUSE_EVENT_MASK);
    }

    @Override
    protected void finalize() throws Throwable {
        super.finalize();

        if(weakAwtEventListener != null) {
            Toolkit.getDefaultToolkit().removeAWTEventListener(weakAwtEventListener);
        }
    }

Ответ 17

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

Ответ 18

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

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

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

Ответ 19

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

Ответ 20

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

Ответ 21

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

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

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