Gymterview
junior

Что такое `finalize()`? Зачем он нужен?

Важно: Метод finalize() объявлен устаревшим начиная с Java 9 (@Deprecated(since="9")) и помечен для удаления начиная с Java 18 (@Deprecated(since="9", forRemoval=true)). Вместо finalize() рекомендуется использовать конструкцию try-with-resources (для объектов, реализующих AutoCloseable) или java.lang.ref.Cleaner API (появился в Java 9) для выполнения действий по освобождению ресурсов.

Через вызов метода finalize() (который наследуется от Java.lang.Object) JVM реализуется функциональность аналогичная функциональности деструкторов в С++, используемых для очистки памяти перед возвращением управления операционной системе. Данный метод вызывается при уничтожении объекта сборщиком мусора (garbage collector) и переопределяя finalize() можно запрограммировать действия необходимые для корректного удаления экземпляра класса - например, закрытие сетевых соединений, соединений с базой данных, снятие блокировок на файлы и т.д.

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

Объект не обязательно будет доступен для сборки сразу же - метод finalize() может сохранить куда-нибудь ссылку на объект. Подобная ситуация называется «возрождением» объекта и считается антипаттерном. Главная проблема такого трюка - в том, что «возродить» объект можно только 1 раз.

Пример:

Пример
public class MainClass {

	public static void main(String args[]) {
		TestClass a = new TestClass();
		a.a();
		a = null;
		a = new TestClass();
		a.a();
		System.out.println("!!! done");
	}
}
Пример

public class TestClass {

	public void a() {
		System.out.println("!!! a() called");
	}

	@Override
	protected void finalize() throws Throwable {
		System.out.println("!!! finalize() called");
		super.finalize();
	}
}

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

Пример
!!! a() called
!!! a() called
!!! done

Теперь несколько усложним программу, добавив принудительный вызов Garbage Collector:

Пример
public class MainClass {

	public static void main(String args[]) {
		TestClass a = new TestClass();
		a.a();
		a = null;
		System.gc(); // Принудительно зовём сборщик мусора
		a = new TestClass();
		a.a();
		System.out.println("!!! done");
	}

}

Как и было сказано ранее, Garbage Collector может в разное время отработать, поэтому результат выполнения может разниться от запуска к запуску: Вариант а:

Пример
!!! a() called
!!! a() called
!!! done
!!! finalize() called

Вариант б:

Пример
!!! a() called
!!! a() called
!!! finalize() called
!!! done