Why does Effective Java advise against using finalizers (and cleaners), and what should you use instead to release resources?
answer
- No timing / no run-at-all guarantee
- Slow + swallows exceptions + finalizer attack
- Replace with AutoCloseable.close()
- try-with-resources = deterministic close
- Cleaner = safety net only, not primary
basics
~10 sFinalizers run only when the garbage collector decides to, which may be very late or never, so they cannot reliably free resources like files or sockets. Instead, implement AutoCloseable and close resources with try-with-resources.
solid answer
~40 sA finalizer is a finalize() method the garbage collector might call before reclaiming an object, but there is no guarantee it runs promptly or at all, so you cannot use it to release scarce resources like file handles or DB connections, which can then leak and exhaust the system. Finalizers also hurt performance, can swallow exceptions, and create security holes. Cleaners (Java 9+) are a safer replacement but still run on the GC's schedule with no timing guarantee, so they only belong as a safety net. The correct approach is to make the class implement AutoCloseable and have callers use try-with-resources, which deterministically calls close() when the block exits, even on exception.
go deeper
Knows finalize() is unreliable/deprecated and that you should close resources with try-with-resources on an AutoCloseable.
Can list concrete reasons (no timing guarantee, may never run, performance, swallowed exceptions) and correctly write a try-with-resources block, including multiple resources.
Explains the Cleaner replacement and its narrow safety-net/native-peer role, suppressed-exception semantics, and the finalizer attack plus its defense.
Reasons about resource-management API design for a library: making ownership explicit, documenting close contracts, deciding when a cleaner backstop is worth its cost, and the GC interaction with native/off-heap resources.
## The problem this idiom solves Some objects own a **resource** that lives outside the Java heap: an open file, a network socket, a database connection, a native (C/C++) memory buffer, a lock. The garbage collector (GC) — the JVM subsystem that automatically reclaims memory for objects no longer reachable — knows how to free *heap memory*, but it knows nothing about these external resources. Something must explicitly release them, and the question is *what* and *when*. ## What a finalizer is A **finalizer** is the method `protected void finalize()` that every object inherits from `java.lang.Object`. Historically you could override it, and the GC would *consider* calling it once, at some unspecified time before reclaiming the object. The intent was "last-chance cleanup." A **cleaner** (`java.lang.ref.Cleaner`, added in Java 9) is the modern replacement: you register an object with a `Cleaner` and supply a cleanup action that runs after the object becomes unreachable. ## Why finalizers (and cleaners) are unreliable 1. **No timing guarantee.** Finalizers run when the GC gets around to them. Between an object becoming unreachable and its finalizer running, an arbitrary amount of time passes. If that object holds the only open file handle and you have thousands of them, you can exhaust the OS file-descriptor limit and crash with errors *before* a single finalizer runs. 2. **No guarantee they run at all.** If the JVM exits, pending finalizers may never execute. So you can never rely on a finalizer to do anything that *must* happen, like flushing a buffer or releasing a lock in a persistent store. 3. **Performance cost.** Objects with finalizers are slower to allocate and collect — the GC must do extra bookkeeping (a separate finalization queue and an extra GC cycle), measured at roughly an order of magnitude slower in *Effective Java*. 4. **Exceptions are swallowed.** An uncaught exception thrown in a finalizer is ignored and finalization of that object just stops, potentially leaving it in a corrupt state with no warning. 5. **Finalizer attacks (a security hole).** If a constructor throws *after* the object is partly built, a malicious subclass can override `finalize()`; the half-constructed object still gets finalized, letting the attacker capture a reference to it. The defense is to make `finalize()` `final` and empty, or avoid it entirely. 6. **Resurrection.** A finalizer can store `this` into a static field, making the object reachable again ("resurrecting" it), which defeats GC and is confusing. Cleaners fix the security and exception problems but still share the core defect: **they run on the GC's schedule, with no timing or run-at-all guarantee.** ## The correct replacement: AutoCloseable + try-with-resources `AutoCloseable` is an interface with one method, `void close() throws Exception`. A class that owns a resource implements it and releases the resource in `close()`. **try-with-resources** is the language construct that guarantees `close()` is called: ```java try (var in = new FileInputStream(path)) { // use in } // in.close() is called here automatically, even if the body throws ``` The resource is closed **deterministically** the moment the `try` block exits — normally or via exception — at exactly the point you choose, not whenever the GC fires. Multiple resources are closed in reverse order of declaration. Exceptions from `close()` are *suppressed* rather than lost (retrievable via `Throwable.getSuppressed()`), so the original exception isn't masked. ## Where cleaners still have a (narrow) place Two legitimate uses remain: (1) a **safety net** — if a caller forgets to call `close()`, a registered cleaner can still release a *native* resource late, which is better than never (the JDK's `FileInputStream`/`Connection` do this) — and (2) cleaning up **native peers** (off-heap memory the GC can't see). Even then, the primary mechanism must be explicit `close()`; the cleaner is only a backstop. ## Bottom line Make resource-owning classes `AutoCloseable`, document that callers must close them, and use try-with-resources. Reserve cleaners (never the deprecated `finalize`) for a best-effort safety net on native resources.
- What is a finalizer attack and how do you defend against it?If a constructor throws after partial construction, a malicious subclass can override finalize() and, when the half-built object is finalized, capture a reference to it — bypassing constructor invariants. Defenses: make the class final, or add a final, empty finalize() so a subclass can't override it. Best of all, don't use finalizers.
- How does try-with-resources handle an exception thrown by close() while the body also threw?The exception from the try body propagates as the primary exception, and the exception from close() is added to it as a suppressed exception, retrievable via getSuppressed(). This avoids the old try/finally bug where the close() exception silently masked the real one.
saying these in an interview costs you the question
- Claiming finalize() runs reliably right after an object becomes unreachable
- Using a finalizer/cleaner as the primary way to free files, sockets, or locks
- Thinking try-with-resources only works for I/O streams (it works for any AutoCloseable)
- Believing cleaners give a timing guarantee that finalizers lack — they don't