Метод finalize() в Java вызывается потоком сборщика мусора перед освобождением памяти, выделенной объекту. Он рассматривается как деструктор в Java – хотя он не похож на то, что мы имеем в C/C++.
Давайте узнаем, когда следует использовать finalize() в Java и почему следует избегать его использования, если это возможно.
JEP-421(Java 18) пометил финализацию как устаревшую. Она будет удалена в будущем выпуске. Рекомендуется использовать очистители.
1. finalize() в Java
1.1 Синтаксис
Он вызывается сборщиком мусора для объекта, когда сборщик мусора определяет, что больше нет ссылок на объект. Подкласс переопределяет метод finalize() для утилизации системных ресурсов или для выполнения других очисток.
Обратите внимание, что finalize() — устаревший метод.
@Устаревшее(с="9")защищенный void finalize() бросает Throwable
1.2 Реализация finalize()
Общий синтаксис для переопределения метода finalize() приведен ниже.
публичный класс MyClass{//Другие поля и методы//...@Переопределитьзащищенный void finalize() бросает Throwable{пытаться{//освободить ресурсы здесь}поймать(Бросаемый т){бросить т;}окончательно{супер.finalize();}}}
2. Выполнение finalize() не гарантируется
Давайте докажем это с помощью программы. Я написал простую исполняемую программу Java с одним оператором печати в каждом блоке, т.е. try-catch-finally-finalize.
Я также создал еще один класс, который создаст 3 экземпляра этого исполняемого файла, а затем мы увидим путь выполнения.
открытый класс TryCatchFinallyTest реализует Runnable {private void testMethod() выдает InterruptedException{пытаться{System.out.println("В блоке try");выдать новое исключение NullPointerException();}catch(NullPointerException npe){System.out.println("В блоке catch");}окончательно{System.out.println("В конце концов блок");}}@Переопределитьзащищенный void finalize() бросает Throwable {System.out.println("В блоке финализации");супер.finalize();}@Переопределитьpublic void run() {пытаться {testMethod();} поймать(InterruptedException e) {e.printStackTrace();}}}
публичный класс TestMain{@SuppressWarnings("устаревание")public static void main(String[] args) {для(int i=1;i<=3;i++){новый поток(new TryCatchFinallyTest()).start();}}}
Вывод программы.
В блоке tryВ блоке catchВ конце концов блокВ блоке tryВ блоке catchВ конце концов блокВ блоке tryВ блоке catchВ конце концов блок
Удивительно, метод finalize() вообще не был выполнен ни для одного потока. Так что это доказывает то, что я уже сказал.
Причина, которую я могу придумать, заключается в том, что финализаторы выполняются отдельным потоком от сборщика мусора. Если JVM завершается слишком рано, то сборщик мусора не успевает создать и выполнить финализаторы.
2.1. Можно ли принудительно выполнить finalize()?
Ответ — да. Да, можем. Using Runtime.runFinalizersOnExit(true);
публичный класс TestMain{@SuppressWarnings("устаревание")public static void main(String[] args) {для(int i=1;i<=3;i++){новый поток(new TryCatchFinallyTest()).start();Runtime.runFinalizersOnExit(истина);}}}
Вывод программы.
В блоке tryВ блоке catchВ конце концов блокВ блоке tryВ блоке tryВ блоке catchВ конце концов блокВ блоке catchВ конце концов блокВ блоке завершенияВ блоке завершенияВ блоке завершения
Есть еще один метод: Runtime.getRuntime().runFinalization(); но он гарантирует только то, что GC приложит все усилия. Даже наша программа не может запустить метод finalize() для всех 3 потоков.
Двигаясь вперед, мы использовали Runtime.runFinalizersOnExit(true). Ну, это еще одна яма. Этот метод уже устарел в JDK по следующей причине –
«Этот метод изначально небезопасен. Он может привести к вызову финализаторов для живых объектов, в то время как другие потоки одновременно манипулируют этими объектами, что приведет к неустойчивому поведению или взаимоблокировке».
Итак, с одной стороны, мы не можем гарантировать выполнение, а с другой — подвергаем систему опасности. Лучше не используйте этот метод.
3. Другие причины не использовать finalize()
- Методы finalize() не работают в цепочке, как конструкторы. Это означает, что когда вы вызываете конструктор, то конструкторы всех суперклассов будут вызваны неявно. Но в случае методов finalize() это не соблюдается. В идеале finalize() родительского класса должен вызываться явно, но этого не происходит.
- Предположим, вы создали класс и тщательно написали его метод finalize. Кто-то приходит и расширяет ваш класс и не вызывает super.finalize() в блоке finalize() подкласса, тогда finalize() суперкласса никогда не будет вызван.
Любое исключение, которое выбрасывается методом finalize(), игнорируется потоком GC и не будет распространяться дальше, фактически оно не будет зарегистрировано в ваших файлах журнала. Плохо, не правда ли?
4. finalize() накладывает серьезные штрафы на производительность
В книге «Эффективная Java»(2-е издание) Джошуа Блох пишет:
«О, и еще одно: использование финализаторов приводит к серьезному снижению производительности. На моей машине время создания и уничтожения простого объекта составляет около 5,6 нс. Добавление финализатора увеличивает время до 2400 нс. Другими словами, создание и уничтожение объектов с финализаторами происходит примерно в 430 раз медленнее».
Я также попробовал вышеприведенный анализ на своей системе, но мне не удалось получить такую большую разницу. Хотя, конечно, какая-то разница есть. Но это было около 30% от исходного времени. В системах, критичных ко времени, это также большая разница.
5. Как правильно использовать finalize()
Если после всех вышеприведенных аргументов вы все еще обнаружите ситуацию, когда использование finalize() необходимо, то перепроверьте следующие пункты:
- Всегда вызывайте super.finalize() в методе finalize().
- Не помещайте в finalize() логику приложения, критичную по времени, учитывая ее непредсказуемость.
- Не используйте Runtime.runFinalizersOnExit(true); так как это может подвергнуть вашу систему опасности.
- Попробуйте следовать приведенному ниже шаблону для метода finalize()
@Переопределитьзащищенный void finalize() бросает Throwable{пытаться{//освободить ресурсы здесь}catch(Бросаемый t){бросить т;}окончательно{супер.finalize();}}
В этой статье мы обсудили лучшие практики финализации Java, как можно вручную вызвать метод finalize в Java и почему не следует использовать finalize в приложениях Java.