← Назад к вопросам

Может ли finalize не вызываться?

1.2 Junior🔥 111 комментариев
#Основы Java

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Может ли finalize не вызваться?

Краткий ответ: ДА, ОЧЕНЬ часто! Метод finalize() — это одна из самых ненадёжных частей Java, и на него категорически не стоит полагаться.

Что такое finalize()?

public class Resource {
    private FileInputStream file;
    
    @Override
    protected void finalize() throws Throwable {
        // Вызовется (может быть) перед удалением объекта
        if (file != null) {
            file.close();
        }
        super.finalize();
    }
}

finalize() — это метод, который JVM может вызвать перед удалением объекта из памяти. Ключевое слово: может.

Когда finalize() НЕ вызывается?

1. Приложение аварийно завершилось

Security.SecurityException se = new SecurityException();
throw se;
// finalize() НЕ будет вызван перед System.exit()

System.exit(0);  // Приложение выходит

2. JVM завершилась

// Перезагрузка, отключение сервера, SIGKILL
System.exit(1);
Runtime.getRuntime().halt(1);

// finalize() не будет вызван

3. Объект никогда не был удален (утечка памяти)

public class MemoryLeak {
    private static List<Object> cache = new ArrayList<>();
    
    public MemoryLeak() {
        cache.add(this);  // Объект остаётся в памяти ВСЕГДА
    }
    
    @Override
    protected void finalize() throws Throwable {
        System.out.println("finalize вызван");
    }
}

// Тест
for (int i = 0; i < 1000; i++) {
    new MemoryLeak();  // finalize НИКОГДА не будет вызван
}
// Объекты остаются в cache, GC не может их удалить

4. GC никогда не запустился

public class Test {
    @Override
    protected void finalize() throws Throwable {
        System.out.println("finalize");
    }
}

// Программа
for (int i = 0; i < 100; i++) {
    new Test();
}
// Если программа заканчивается быстро, GC может не запуститься
// finalize() не будет вызван

5. Объект всё ещё достижим (referenced)

public class Example {
    @Override
    protected void finalize() throws Throwable {
        System.out.println("finalize");
    }
}

// Код
Example ex = new Example();
System.gc();  // Попытка запустить GC
// finalize НЕ будет вызван, потому что ex всё ещё в памяти!

ex = null;  // Теперь можно
System.gc();
// Теперь finalize МОЖЕТ быть вызван

6. Программа слишком быстро завершается

public class QuickExit {
    private int id;
    
    public QuickExit(int id) {
        this.id = id;
    }
    
    @Override
    protected void finalize() throws Throwable {
        System.out.println("Cleanup: " + id);
    }
}

// Тест
for (int i = 0; i < 100; i++) {
    new QuickExit(i);
}
// Программа заканчивается
// Вывод: может быть пусто или только несколько вызовов

Визуальное представление: Когда finalize() вызывается

Объект создан
    ↓
    ├─ Объект достижим (на него есть ссылка)
    │  └─ finalize() НЕ вызывается
    │
    └─ Объект НЕДОСТИЖИМ (нет ссылок)
       ↓
       ├─ GC НЕ запустился
       │  └─ finalize() НЕ вызывается
       │
       └─ GC ЗАПУСТИЛСЯ
          ↓
          ├─ finalize() МОЖЕТ быть вызван (не гарантировано)
          │
          └─ Объект удалён

Программа завершилась
    ↓
    └─ finalize() НЕ вызывается для объектов в памяти

Известные проблемы

Проблема 1: Финализация в многопоточной среде

public class SharedResource {
    private Connection dbConnection;
    
    @Override
    protected void finalize() throws Throwable {
        // ❌ finalize вызывается в ОТДЕЛЬНОМ потоке (Finalizer thread)
        // Может быть race condition если dbConnection используется!
        dbConnection.close();
        super.finalize();
    }
}

Проблема 2: finalize() может переживать объект

public class Resurrection {
    private static Resurrection instance;
    
    @Override
    protected void finalize() throws Throwable {
        // Спасаем объект от удаления!
        instance = this;  // Объект снова достижим
    }
}

// Тест
instance = new Resurrection();
instance = null;
System.gc();
// finalize() вызовется И спасёт объект
// Он снова будет в памяти!

// Используем
instance.doSomething();  // Работает!

Проблема 3: Backlash и performance

// ❌ ПЛОХО
class BadResource {
    protected void finalize() throws Throwable {
        cleanup();
    }
}

// Если много объектов с finalize():
for (int i = 0; i < 1_000_000; i++) {
    new BadResource();  // Создаём миллион объектов
}
// GC будет перегружена финализацией
// Производительность упадёт кратно

Что делать вместо finalize()?

Вариант 1: Try-with-resources (Java 7+) — РЕКОМЕНДУЕТСЯ

public class ManagedResource implements AutoCloseable {
    private FileInputStream file;
    
    @Override
    public void close() throws IOException {
        if (file != null) {
            file.close();
        }
    }
}

// Использование
try (ManagedResource resource = new ManagedResource()) {
    // Работаем с ресурсом
} // close() ГАРАНТИРОВАННО вызовется

Вариант 2: Явное закрытие (явно в коде)

public class ManualResource {
    private FileInputStream file;
    
    public void close() throws IOException {
        if (file != null) {
            file.close();
        }
    }
}

// Использование
ManualResource resource = new ManualResource();
try {
    // Работаем
} finally {
    resource.close();  // Явно вызываем
}

Вариант 3: Cleaner (Java 9+) — для особых случаев

import java.lang.ref.Cleaner;

public class CleanerResource {
    private static final Cleaner cleaner = Cleaner.create();
    
    private final Cleaner.Cleanable cleanable;
    
    public CleanerResource() {
        cleanable = cleaner.register(this, () -> {
            System.out.println("Cleanup");
            // cleanup code
        });
    }
    
    public void close() {
        cleanable.clean();
    }
}

Практический совет: Никогда не используйте finalize()

// ❌ ИЗБЕГАТЬ
public class OldWay {
    @Override
    protected void finalize() throws Throwable {
        // Ненадёжно!
    }
}

// ✅ РЕКОМЕНДУЕТСЯ
public class NewWay implements AutoCloseable {
    @Override
    public void close() throws IOException {
        // Ясно и надёжно
    }
}

Статистика: Когда finalize() действительно вызывается

// Реальный тест
class TestFinalize {
    private static int count = 0;
    
    @Override
    protected void finalize() throws Throwable {
        count++;
    }
}

// Создаём 10000 объектов
for (int i = 0; i < 10000; i++) {
    new TestFinalize();
}

// Пытаемся вызвать GC
for (int i = 0; i < 10; i++) {
    System.gc();
    Thread.sleep(100);
}

System.out.println("finalize() вызван " + count + " раз");
// Вывод: иногда 9000, иногда 10000, иногда 0
// Полностью непредсказуемо!

Итог

Может ли finalize не вызваться?

ДА, очень часто:

  • Program exit / System.exit()
  • GC не запустилась
  • Объект остался в памяти (утечка)
  • Слишком быстрое завершение программы
  • Объект остаётся достижимым

finalize() — это LEGACY код Java. Не используйте!

Используйте вместо этого:

  • try-with-resources (AutoCloseable)
  • Явные методы close()
  • Cleaner (Java 9+) — если очень нужно

Правило: finalize() вызовется когда угодно, или никогда. На него нельзя полагаться!

Может ли finalize не вызываться? | PrepBro