Why was finalize() deprecated, and what are its replacements for resource cleanup?
answer
- Deprecated Java 9; removal JEP 421 (Java 18)
- Flaws: timing, perf, swallowed exception, resurrection, security
- Primary: AutoCloseable + try-with-resources
- Backstop: Cleaner (no back-reference)
- Cleaner > finalize but still not deterministic
basics
~20 sfinalize() is unreliable: you can't predict when it runs, it may never run, it slows down GC, and it has dangerous edge cases. Java deprecated it in 9. Use try-with-resources (AutoCloseable) for deterministic cleanup and Cleaner as a safety net.
solid answer
~50 sfinalize() was deprecated in Java 9 (and later deprecated for removal via JEP 421 in Java 18) because it is fundamentally unreliable. There's no guarantee about when it runs or that it runs at all before JVM exit; it forces finalizable objects through at least two GC cycles, hurting performance and delaying memory reclamation; an exception thrown inside it is swallowed and aborts the rest of the cleanup; and it enables resurrection and finalizer-attack security holes. The replacements: for deterministic cleanup, implement AutoCloseable and let callers use try-with-resources, so close() runs at a known point even on exception. For a safety net against callers who forget to close (especially for native/off-heap resources), register a java.lang.ref.Cleaner action — but treat it strictly as a backstop, never the primary mechanism. The combination of these makes finalize() obsolete.
go deeper
Knows finalize() is deprecated and unreliable, and that try-with-resources is the modern way to clean up resources.
Lists the main reasons (no timing guarantee, performance, may not run) and names AutoCloseable/try-with-resources and Cleaner as replacements with correct roles.
Covers all flaws including exception swallowing and resurrection, the JEP 421 removal path, and the close()-primary / Cleaner-backstop discipline including the no-back-reference rule.
Sets organization-wide policy banning finalize(), designs resource-ownership abstractions, weighs native-resource lifecycle and security (finalizer attacks), and plans migration ahead of finalization's platform removal.
## The setup In **Java**, objects are freed automatically by the **garbage collector (GC)** once they're **unreachable** (nothing live points to them). `finalize()` — a method inherited from `java.lang.Object` — was meant to let an object release external resources (open files, sockets, **native memory**) just before reclamation. Decades of experience showed this idea is broken in practice. ## Why it was deprecated — the concrete problems 1. **No timing or execution guarantee.** The **JLS** only promises `finalize()` runs *at most once*, *before* reclamation. It says nothing about *when*. Under low memory pressure it may not run for a long time; if the **JVM exits** first, queued finalizers may never run at all. So you cannot use it to reliably flush a buffer or close a connection. 2. **Performance cost.** A finalizable object can't be reclaimed on the cycle that first finds it unreachable; the GC must keep it alive, queue it, run `finalize()` on a single low-priority **finalizer thread**, and only reclaim it on a *later* cycle. This roughly multiplies create/destroy cost and delays memory reclamation, increasing pressure and potentially `OutOfMemoryError`. 3. **Exception swallowing.** If `finalize()` throws, the finalizer thread *abandons* finalization of that object — the remaining cleanup silently doesn't happen, and there's no useful warning. State can be left half-cleaned. 4. **Resurrection.** `finalize()` can store `this` somewhere reachable, reviving the object; since `finalize()` runs at most once, its later cleanup is then skipped. (A correctness hole.) 5. **Security — finalizer attacks.** If a constructor throws after partially initializing an object, a malicious subclass's `finalize()` can still run on that half-built instance and access it. Defending classic code required an awkward *finalizer guardian* idiom. Removing finalization removes the attack surface. ## The deprecation timeline - **Java 9:** `Object.finalize()` marked `@Deprecated`. - **Java 18 (JEP 421):** finalization deprecated **for removal**, with command-line flags to disable it, on a path to permanent removal from the platform. ## The replacements ### 1. Deterministic cleanup — `AutoCloseable` + try-with-resources This is the *primary* answer. A resource-owning class implements `java.lang.AutoCloseable` and puts cleanup in `close()`. Callers use **try-with-resources**, which guarantees `close()` is invoked at the end of the block, in reverse order of opening, even if an exception is thrown (suppressed exceptions are attached, not lost): ```java try (var conn = dataSource.getConnection(); var stmt = conn.prepareStatement(sql)) { // ... use stmt } // stmt.close() then conn.close() called here, deterministically ``` Deterministic = you know *exactly* when cleanup happens. This is the opposite of `finalize()` and is how virtually all JDK resources (streams, channels, JDBC) should be handled. ### 2. Safety net — `java.lang.ref.Cleaner` Sometimes you also want cleanup to happen *if the caller forgets* to call `close()` — especially for **native resources** that the GC can't see. `Cleaner` (Java 9+) provides this without `finalize()`'s flaws. You register a cleanup `Runnable` (which must **not** reference the owning object) with a shared `Cleaner`; when the object becomes phantom-reachable, the action runs on a Cleaner thread: ```java class Buffer implements AutoCloseable { private static final Cleaner CLEANER = Cleaner.create(); private final Cleaner.Cleanable cleanable; Buffer(long nativePtr) { // state holder must not capture 'this' this.cleanable = CLEANER.register(this, () -> free(nativePtr)); } public void close() { cleanable.clean(); } private static native void free(long ptr); } ``` `Cleaner` is more predictable than `finalize()` (no resurrection, no at-most-once trap, no swallowed exceptions corrupting state) but is **still not deterministic** — it depends on the GC. So the rule is: *always* provide `close()` and use try-with-resources; add a `Cleaner` only as a backstop. ## Summary table | Need | Use | |---|---| | Deterministic cleanup at a known point | `AutoCloseable` + try-with-resources | | Safety net if caller forgets close() (native resources) | `Cleaner` (action holds no back-reference) | | Anything | **Not** `finalize()` | **Bottom line:** `finalize()` is deprecated for removal because it is unpredictable, slow, and dangerous. Make resources `AutoCloseable`, manage them with try-with-resources, and add a `Cleaner` only as a last line of defense.
- Is Cleaner deterministic? When should you still implement close()?No — Cleaner depends on the GC, so it's not deterministic. You should always implement close() (via AutoCloseable) for deterministic cleanup, and add a Cleaner only as a safety net for callers who forget to close, particularly for native/off-heap resources.
- What happens if an exception is thrown inside finalize()?It is effectively swallowed: the finalizer thread aborts finalization of that object and the remaining cleanup is skipped, with no useful error surfaced, potentially leaving the object in a corrupt state.
saying these in an interview costs you the question
- Recommending finalize() in new code 'just in case'
- Saying Cleaner gives deterministic cleanup (it does not)
- Forgetting to also implement close() and relying only on a Cleaner
- Claiming finalize() was removed in Java 9 (it was only deprecated then)