Питання 11 · Розділ 7

Що таке інтерфейс AutoCloseable?

AutoCloseable з'явився в Java 7 спеціально для try-with-resources. Closeable (Java 5) був лише для I/O потоків. AutoCloseable -- у java.lang (без імпортів), щоб будь-який ресурс...

Мовні версії: English Russian Ukrainian

Junior Level

Визначення

java.lang.AutoCloseable — це інтерфейс з одним методом close(), який викликається автоматично при виході з блоку try-with-resources.

AutoCloseable з’явився в Java 7 спеціально для try-with-resources. Closeable (Java 5) був лише для I/O потоків. AutoCloseable – у java.lang (без імпортів), щоб будь-який ресурс міг стати «закриваним».

public interface AutoCloseable {
    void close() throws Exception;
}

Як використовувати

// FileInputStream реалізує AutoCloseable
try (FileInputStream fis = new FileInputStream("file.txt")) {
    fis.read();
} // fis.close() викличеться автоматично

Які класи його реалізують

  • InputStream / OutputStream — файли, мережа
  • Connection / Statement — база даних
  • Reader / Writer — текст
  • Scanner — утиліти

Навіщо потрібен

Щоб не забувати закривати ресурси. До Java 7 доводилося писати finally з close() вручну.


Middle Level

Чому у java.lang?

Це фундаментальний інтерфейс. Його винесли у java.lang, щоб він був доступний скрізь без імпортів — на рівні Runnable або Iterable.

Правильна реалізація close()

1. Звуження типу винятку: Інтерфейс оголошує throws Exception. Перевизначайте з конкретним типом:

public class MyResource implements AutoCloseable {
    @Override
    public void close() throws IOException { // конкретніше
        // закриття
    }
}

Або взагалі без throws, якщо метод безпечний.

2. Патерн “Safe Close”: Якщо ресурс володіє кількома під-ресурсами:

public void close() throws Exception {
    try {
        subResource1.close();
    } finally {
        subResource2.close(); // закриється навіть якщо перший впав
    }
}

3. Ідемпотентність: Повторний виклик close() не повинен викидати виняток.

Garbage Collector та AutoCloseable

AutoCloseable не замінює управління пам’яттю. Ресурс (файл, сокет) — це нативний дескриптор в ОС. Якщо не викликати close(), дескриптор “висить” поки GC не збере об’єкт.

Це веде до помилки “Too many open files”.


Три підходи до управління ресурсами

Механізм Коли спрацьовує Гарантія Рекомендація
TWR При виході з блоку ✅ Гарантує Основний спосіб
Cleaner Коли GC вирішить ⚠️ Страховка Фолбек, якщо забули close()
finalize() Коли GC вирішить ❌ Непередбачуваний Застарів (deprecated у Java 9)

Senior Level

Under the Hood: нативні дескриптори

Ресурс — це не Java-об’єкт, а дескриптор операційної системи. Дескриптор – число (int), яке ОС видає при відкритті файла/сокета. Java-об’єкт – лише обгортка. GC бачить обгортку, але не бачить нативний ресурс в ОС. Без явного close() дескриптор утече, навіть якщо GC зібрав Java-об’єкт. GC не бачить нативні ресурси. Без close() дескриптор витікає, навіть якщо Java-об’єкт зібраний.

Atomic State у багатопоточних середовищах

public class MyResource implements AutoCloseable {
    private final AtomicBoolean closed = new AtomicBoolean(false);

    public void close() {
        if (closed.compareAndSet(false, true)) {
            // реальне звільнення ресурсів
        }
    }
}

Два потоки не повинні закрити один сокет одночасно — невизначена поведінка на рівні нативного драйвера.

Кидання винятків у конструкторі

Якщо конструктор кинув виняток після відкриття нативного ресурсу, але до повернення об’єкта — витік:

public class MyResource implements AutoCloseable {
    private final NativeHandle handle;

    public MyResource(String path) {
        this.handle = openNative(path); // відкрили
        if (!validate(path)) {
            throw new IllegalArgumentException(); // handle утек!
        }
    }
}

Рішення: легкі конструктори, важке відкриття — під try-with-resources.

Cleaner замість Finalizer (Java 9+)

finalize() deprecated. Для “страховки” ресурсів використовуйте java.lang.ref.Cleaner:

public class MyResource implements AutoCloseable {
    private static final Cleaner cleaner = Cleaner.create();
    private final Cleaner.Cleanable cleanable;

    public MyResource() {
        this.cleanable = cleaner.register(this, () -> {
            // останній рубіж — якщо не викликали close()
            System.err.println("Resource not closed properly!");
        });
    }

    public void close() {
        cleanable.clean();
    }
}
// Cleaner запускається GC. Коли на MyResource більше немає сильних посилань,
// GC позначає об'єкт для очищення. Cleaner виконає лямбду в окремому потоці.
// Це НЕ заміна TWR -- GC може спрацювати через хвилини або навіть години.
// Cleaner -- страховка, якщо забули close().

Це лише страховка, не скасовує важливості TWR.

Діагностика

  • SonarLint Rule — “Resources should be closed”. Перевіряє ієрархію типів.
  • jcmd VM.native_memory — моніторинг нативної пам’яті
  • Витік дескрипторів — часта причина падіння JVM через дні роботи

🎯 Шпаргалка для співбесіди

Обов’язково знати:

  • AutoCloseable з’явився в Java 7 спеціально для try-with-resources
  • Єдиний метод: void close() throws Exception
  • Знаходиться у java.lang — доступний без імпортів, як Runnable
  • close() повинен бути ідемпотентним (повторний виклик безпечний)
  • GC не бачить нативні дескриптори — без close() витік навіть при зібраному об’єкті
  • Звужуйте throws Exception до конкретного типу або прибирайте зовсім
  • Cleaner (Java 9+) — страховка, але НЕ заміна TWR

Часті уточнюючі запитання:

  • Чому у java.lang, а не в java.io? — Щоб будь-який клас міг стати закриваним без імпортів, на рівні Object
  • Що буде якщо не закрити ресурс? — Витік нативних дескрипторів → “Too many open files” → падіння JVM
  • Чи може close() кинути виняток? — Так, тому у multi-resource TWR винятки придушуються (suppressed)
  • Чим Cleaner кращий за finalize()? — Cleaner передбачуваніший, finalize() deprecated у Java 9

Червоні прапорці (НЕ говорити):

  • “GC сам закриє ресурси” — GC збирає Java-об’єкт, але не нативний дескриптор
  • “AutoCloseable лише для I/O” — Це Closeable; AutoCloseable для будь-яких ресурсів
  • “Можна не робити close() ідемпотентним” — Javadoc наполегливо рекомендує, повторний виклик не повинен падати

Пов’язані теми:

  • [[В чому різниця між AutoCloseable та Closeable]]
  • [[Що таке try-with-resources]]
  • [[Які вимоги до ресурсів в try-with-resources]]
  • [[Що таке обгортання винятків (wrapping)]]
  • [[Що станеться, якщо в блоку finally теж виникне виняток]]