Урок 18. Сборщик мусора и метод finalize в Java


В этой статье из моего бесплатного курса Java я буду обсуждать метод Object finalize() в Java. Объект class, который является суперклассом всех классов, определяет метод finalize(), а также другие методы, включая clone(), toString(), hashCode() и equals().

Что такое finalize?

finalize() - это хук метод объекта класса.

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

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

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

Сборщик мусора

Сборщик мусора, как вы можете себе представить, собирает "мусор" в вашей программе. Мусор, который очищается, - это объекты, которые вы создали, обработали, а затем отложили в сторону, когда они вам больше не нужны. Главная обязанность сборщика мусора - освободить ресурсы памяти, чтобы ваша программа, как мы надеемся, никогда не заканчивалась. Однако сборщик мусора работает асинхронно, и мы не имеем никакого контроля или влияния на то, будет ли он работать и когда. Мы можем дать ему рекомендации, но это всего лишь рекомендации. Сборщик мусора не связан ими.

Компьютер использует несколько типов аппаратных ресурсов, и они физически ограничены природой. Поэтому мы должны разумно использовать эти ресурсы. В Java, в отличие от более старых языков программирования, таких как C++, память автоматически управляется сборщиком мусора; однако существуют и другие ресурсы, обычно “ресурсы ввода-вывода”, которыми мы должны управлять самостоятельно.

“I” или “IO” входящие (ввод) данные, и “O” – вывод. Таким образом, IO-это общий термин для функциональности ввода или вывода (также называемой коммуникацией) из такого ресурса, как файл, внешняя система или база данных. Входные данные считываются в нашу систему, а выходные данные записываются из нашей системы. Типичное имя класса или интерфейса, используемого для чтения входных данных, - “Reader”, в то время как типичное имя класса или читателя, используемого для записи выходных данных, - “Writer”.

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

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

Переопределение Finalize()

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

public class Porsche {

     public void finalize(){

     }

}
Аннотация @Override

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

Это помогает нам сразу же определить во время компиляции, еще находясь в нашей IDE, проблему, которая может привести к неожиданным результатам во время выполнения (например, наша “переопределенная” функция не вызывается). Вот почему всякий раз, когда вы переопределяете метод, вы всегда должны добавлять аннотацию @override.

public class Porsche {

     IOReader ioReader = new IOReader();

     @Override
     public void finalize(){
          ioReader.close();
     }
}
Внутри нашего метода finalize я написал код, который вызывает метод close() для всех объектов, которые создает объект класса Porsche. Предположим, например, что у нас есть IOReader, который читает поток символов из файла. Как только мы закончим, мы хотели бы закрыть этот ридер, вызвав метод close(), предоставленный IOReader. Теперь у нас есть наш перезаписанный метод finalize ().

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

Альтернатива финализации

Рекомендуемой альтернативой было бы создать наш собственный метод close(), который очищает/закрывает все ресурсы, которые больше не используются, в данном случае IoReader. Таким образом, мы имеем больший контроль над нашими ресурсами и не зависим от сборщика мусора.

public class Porsche {

     IOReader ioReader = new IOReader();

     public void close(){
          ioReader.close();
     }
}
Давайте также напишем черновик закрытия объекта в классе CarSelector. Во-первых, давайте создадим класс CarSelector и добавим объект Porsche со следующей строкой: Porsche porsche = new Porsche(); и используем наш новый метод close(). Мы окружим try, catch, finally заблокируем и используем наш последний блок, чтобы очистить наш Porsche. Критическим моментом здесь является то, что у вас есть раздел finally в конце, потому что самое классное в finally заключается в том, что даже если возникает исключение, оно гарантированно всегда будет выполнено. Это именно то, что нам нужно, чтобы решить нашу проблему и убедиться, что все ресурсы, не связанные с памятью, всегда свободны. Вот как вы можете правильно сделать это на Java:

public class CarSelector {
 
     public static void main(String[] arguments) {
          Porsche porsche = new Porsche();
 
          try {
               // some code
          } finally {
               porsche.close();
          }
     }
}
Таким образом, мы гарантируем, что, как только мы закончим с нашим объектом porsche, мы закроем все связанные с ним ресурсы, не перезаписывая finalize(). Блок finally делает именно то, что мы хотели, чтобы наш метод finalize() сделал.

Метод finalize() крайне несовершенен. У сборщика мусора есть собственный разум, и мы не можем по-настоящему контролировать, когда и как он работает. Поскольку переопределение метода finalize() эффективно не решает нашу проблему, вы можете вместо этого использовать блок try-(catch)-finally, чтобы закрыть любые дополнительные ресурсы, когда вы закончите с объектом. Поскольку метод close - это наш собственный метод, не связанный со сборщиком мусора, он работает точно так, как задумано, каждый раз.
Автор этого материала - я - Пахолков Юрий. Я оказываю услуги по написанию программ на языках Java, C++, C# (а также консультирую по ним) и созданию сайтов. Работаю с сайтами на CMS OpenCart, WordPress, ModX и самописными. Кроме этого, работаю напрямую с JavaScript, PHP, CSS, HTML - то есть могу доработать ваш сайт или помочь с веб-программированием. Пишите сюда.

тегистатьи IT, уроки по java, java, сборщик мусора, финализация




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




Алгоритм Soundex на C#
Приключения детей после смерти в другом мире
Урок 4. Как выполняется PHP?