Урок 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 - то есть могу доработать ваш сайт или помочь с веб-программированием. Пишите сюда.
Отправляя сообщение я подтверждаю, что ознакомлен и согласен с политикой конфиденциальности данного сайта.