Есть ли деструктор для Java? - PullRequest
       59

Есть ли деструктор для Java?

549 голосов
/ 05 октября 2008

Есть ли деструктор для Java? Кажется, я не могу найти никакой документации по этому вопросу. Если нет, как я могу добиться того же эффекта?

Чтобы сделать мой вопрос более конкретным, я пишу приложение, которое работает с данными, и в спецификации говорится, что должна быть кнопка «сброс», которая возвращает приложение в исходное только что запущенное состояние. Однако все данные должны быть «живыми», если приложение не закрыто или не нажата кнопка сброса.

Будучи обычно программистом на C / C ++, я подумал, что это будет тривиально реализовать. (И, следовательно, я планировал реализовать его в последний раз.) Я структурировал свою программу так, чтобы все объекты, способные к сбросу, были в одном классе, чтобы я мог просто уничтожить все «живые» объекты при нажатии кнопки сброса.

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

Ответы [ 20 ]

491 голосов
/ 05 октября 2008

Поскольку Java является языком для сборки мусора, вы не можете предсказать, когда (или даже если) объект будет уничтожен. Следовательно, не существует прямого эквивалента деструктора.

Существует унаследованный метод, называемый finalize, но он вызывается полностью на усмотрение сборщика мусора. Поэтому для классов, которым необходимо явно привести в порядок, соглашение состоит в том, чтобы определить метод close и использовать finalize только для проверки работоспособности (т. Е. Если close не был вызван, сделайте это сейчас и зарегистрируйте ошибка).

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

114 голосов
/ 02 декабря 2011

Если вы используете Java 7, взгляните на оператор try-with-resources . Например:

try (BufferedReader br = new BufferedReader(new FileReader(path))) {
  System.out.println(br.readLine());
} catch (Exception e) {
  ...
} finally {
  ...
}

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

Это утверждение более ограничено, чем finalize, с точки зрения структурирования кода, но в то же время делает код более простым для понимания и поддержки. Кроме того, нет никакой гарантии, что метод finalize будет вызван вообще во время работы приложения.

107 голосов
/ 05 октября 2008

Нет, здесь нет деструкторов. Причина в том, что все объекты Java выделены в куче и мусор. Без явного освобождения (т. Е. Оператора удаления в C ++) не существует разумного способа реализации реальных деструкторов.

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

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

26 голосов
/ 05 октября 2008

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

Если вам требуется вызов освобождения в вашем объекте, скажем, для освобождения ресурсов, используйте явный вызов метода. Это соглашение можно увидеть в существующих API (например, Closeable , Graphics.dispose () , Widget.dispose () ) и обычно вызывается через try / finally .

Resource r = new Resource();
try {
    //work
} finally {
    r.dispose();
}

Попытки использовать удаленный объект должны вызвать исключение времени выполнения (см. IllegalStateException ).


EDIT:

Я думал, если бы все, что я делал, было просто разыменовать данные и ждать сборщик мусора, чтобы собрать их, не было бы утечки памяти, если мой пользователь неоднократно вводил данные и нажал кнопку сброса?

Как правило, все, что вам нужно сделать, это разыменовать объекты - по крайней мере, так оно и должно работать. Если вас беспокоит сборка мусора, ознакомьтесь с Java SE 6 HotSpot [tm] Настройка сборки мусора виртуальной машины (или эквивалентный документ для вашей версии JVM).

22 голосов
/ 30 июля 2012

С выпуском Java 1.7 у вас теперь есть дополнительная опция использования блока try-with-resources. Например,

public class Closeable implements AutoCloseable {
    @Override
    public void close() {
        System.out.println("closing..."); 
    }
    public static void main(String[] args) {
        try (Closeable c = new Closeable()) {
            System.out.println("trying..."); 
            throw new Exception("throwing..."); 
        }
        catch (Exception e) {
            System.out.println("catching..."); 
        }
        finally {
            System.out.println("finalizing..."); 
        } 
    }
}

Если вы выполните этот класс, c.close() будет выполняться, когда оставлен блок try и до выполнения блоков catch и finally. В отличие от метода finalize(), close() гарантированно будет выполнен. Однако нет необходимости выполнять его явно в предложении finally.

14 голосов
/ 05 октября 2008

Я полностью согласен с другими ответами, говоря, что не следует полагаться на выполнение финализации.

В дополнение к блокам try-catch-finally вы можете использовать Runtime # addShutdownHook (введено в Java 1.3) для выполнения окончательной очистки вашей программы.

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

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

9 голосов
/ 05 октября 2008

Нет, java.lang.Object#finalize - ближайший, который вы можете получить.

Однако, когда (и если) он вызывается, это не гарантируется.
См .: java.lang.Runtime#runFinalizersOnExit(boolean)

6 голосов
/ 05 октября 2008

Во-первых, обратите внимание, что, поскольку Java является сборщиком мусора, редко нужно что-либо делать с уничтожением объектов. Во-первых, потому что у вас обычно нет никаких управляемых ресурсов для освобождения, а во-вторых, потому что вы не можете предсказать, когда или если это произойдет, поэтому это неуместно для вещей, которые должны произойти, «как только никто больше не использует мой объект ».

Вы можете получить уведомление после того, как объект был уничтожен с помощью java.lang.ref.PhantomReference (на самом деле, сказать, что он был уничтожен, может быть немного неточным, но если фантомная ссылка на него находится в очереди, он больше не подлежит восстановлению, что обычно это одно и то же). Общее использование:

  • Разделите ресурсы в вашем классе, которые нужно разрушить, на другой вспомогательный объект (обратите внимание, что если все, что вы делаете, это закрытие соединения, что является распространенным случаем, вам не нужно писать новый класс: в этом случае закрываемое соединение будет «вспомогательным объектом».
  • Когда вы создаете свой основной объект, создайте для него также PhantomReference. Либо обратитесь к новому вспомогательному объекту, либо настройте карту из объектов PhantomReference к соответствующим вспомогательным объектам.
  • После того, как основной объект собран, PhantomReference ставится в очередь (или, скорее, он может быть поставлен в очередь - как финализаторы, нет никаких гарантий, что он когда-либо будет, например, если виртуальная машина выйдет, то она не будет ждать). Убедитесь, что вы обрабатываете его очередь (либо в специальном потоке, либо время от времени). Из-за жесткой ссылки на вспомогательный объект вспомогательный объект еще не был собран. Поэтому сделайте все, что вам угодно, для объекта-помощника, затем отбросьте PhantomReference, и помощник в конечном итоге тоже будет собран.

Существует также finalize (), который выглядит как деструктор, но не ведет себя как один. Обычно это не очень хороший вариант.

6 голосов
/ 21 мая 2009

Извините, если это отклоняется от основной темы, но документация java.util.Timer (SE6) гласит:

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

Я хотел бы вызвать отмену, если класс, владеющий Таймером, потерял свою последнюю ссылку (или иммедиталеский ранее). Здесь надежный деструктор может сделать это для меня. Комментарии выше указывают на то, что в конце концов это плохой выбор, но есть ли элегантное решение? Бизнес "... способный удержать приложение от прекращения ..." не привлекателен.

5 голосов
/ 13 мая 2016

Я согласен с большинством ответов.

Вы не должны полностью зависеть от finalize или ShutdownHook

завершить

  1. JVM не гарантирует, когда будет вызван этот метод finalize().

  2. finalize() вызывается потоком GC только один раз, если объект восстанавливается из метода завершения, тогда финализация не будет вызываться снова.

  3. В вашем приложении могут быть живые объекты, для которых сборка мусора никогда не запускается.

  4. Любое исключение генерируется методом финализации, игнорируется потоком GC

  5. System.runFinalization(true) и Runtime.getRuntime().runFinalization(true) увеличивают вероятность вызова метода finalize(), но теперь эти два метода устарели. Эти методы очень опасны из-за недостаточной безопасности потока и возможного создания тупика.

shutdownHooks

public void addShutdownHook(Thread hook)

Регистрирует новый хук отключения виртуальной машины.

Виртуальная машина Java отключается в ответ на два вида событий:

  1. Программа завершается нормально, когда завершается последний поток, не являющийся демоном, или когда вызывается метод выхода (эквивалентно System.exit), или
  2. Виртуальная машина завершает работу в ответ на пользовательское прерывание, такое как ввод ^ C, или общесистемное событие, такое как выход пользователя из системы или завершение работы системы.
  3. Хук отключения - это просто инициализированный, но незапущенный поток. Когда виртуальная машина начинает свою последовательность выключения, она запускает все зарегистрированные обработчики завершения работы в неустановленном порядке и позволяет им запускаться одновременно. Когда все перехватчики завершены, он запустит все непроверенные финализаторы, если финализация при выходе была включена.
  4. Наконец, виртуальная машина остановится. Обратите внимание, что потоки демона будут продолжать работать во время последовательности выключения, как и потоки, не являющиеся демонами, если завершение работы было инициировано путем вызова метода выхода.
  5. Отключающие крюки также должны быстро закончить свою работу. Когда программа вызывает выход, ожидается, что виртуальная машина быстро отключится и выйдет.

    Но даже в документации Oracle указано, что

В редких случаях виртуальная машина может прерваться, то есть прекратить работу без выключения полностью

Это происходит, когда виртуальная машина прерывается извне, например, с помощью сигнала SIGKILL в Unix или вызова TerminateProcess в Microsoft Windows. Виртуальная машина также может прервать работу, если собственный метод не работает, например, из-за повреждения внутренних структур данных или попытки доступа к несуществующей памяти. Если виртуальная машина прерывается, то нельзя дать никаких гарантий относительно того, будут ли запущены какие-либо перехватчики завершения работы.

Заключение : используйте try{} catch{} finally{} блоки соответствующим образом и высвободите критические ресурсы в блоке finally(}. Во время освобождения ресурсов в блоке finally{}, поймать Exception и Throwable.

...