Зачем вам вообще реализовывать метод Finalize()?

StackOverflow https://stackoverflow.com/questions/158174

  •  03-07-2019
  •  | 
  •  

Вопрос

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

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

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

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

Это было полезно?

Решение

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

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

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

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

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

Другие советы

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Его никогда не следует использовать для очистки вещей «быстрее».

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.

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

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

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

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

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

«Мышление Экеля на Java» хороший раздел на этом.

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

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

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

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

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

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

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

Редактировать:Ладно, это действительно не работает.Я реализовал его и подумал, что если иногда это не удается, для меня это нормально, но он даже не вызвал метод финализации ни разу.

Я не профессиональный программист, но в моей программе есть случай, который я считаю примером хорошего использования 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(this);

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

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

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

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

В качестве примечания:

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

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

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

В Java 7 у нас есть попробовать с ресурсами утверждение, которое можно использовать как:

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

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

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

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

Таким образом, даже в этой ситуации вызов Finalize будет нет быть рекомендовано.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top