Why exactly PhantomReference should be preferred to finalize?

430 Views Asked by At

They both can be used for cleanup, there is almost no guarantees, but PR requires more harness coding. So, having two options, why exactly I have to prefer one to another?

Javadoc 9 describes finalize as very problematic, but that doesn't make its alternative better automatically, right?

Also javadoc describes PhantomReference as providing "more flexible and efficient ways to release resources when an object becomes unreachable", but without a reason specified. Well, I guess these guys know some secrets, but I'm wondering - can't be this choice made more obvious?

The difference

Here are all the differences between finalize (FZ) and pantom reference (PR) I discovered, please correct me if I missed something.

  1. Can be used for cleanup actions?

    • Yes for both.
  2. Requires a new thread to maintain?

    • PR: yes, you must define a queue watcher thread in order to do cleanup ASAP
    • FZ: no
  3. Requires a new class to define?

    • PR: yes, you must extend PhantomReference to act meaningfully
    • FZ: no
  4. Can cleanup processor access the referent object?

    • PR: no
    • FZ: yes, and that's handy
  5. Does it work reliably in my personal practice?

    • Yes for both.
  6. Can lead to performance issues, deadlocks, and hangs?

    • Yes for both. Depends on your code, isn't?
  7. Can errors in a cleanup processor lead to resource leaks?

    • Yes for both. Depends on your code, isn't?
  8. Cancelable if it is no longer necessary?

    • PR: yes
    • FZ: no, if speaking strictly, but is immediate return that bad?
  9. Is invocation ordering between multiple instances specified?

    • PR: no info
    • FZ: no - "no ordering is specified among calls to finalize methods of different objects" (java.lang.Object)
  10. Invocation guaranteed?

    • PR: no info - you can only "request to be notified of changes in an object's reachability" (java.lang.ref)
    • FZ: no - "The finalize method might be called on a finalizable object only after an indefinite delay, if at all" (java.lang.Object)
  11. Any guarantees regarding the timing?

    • PR: no - "Some time after the garbage collector determines that the reachability of the referent has changed to the value corresponding to the type of the reference" (java.lang.ref)
    • FZ: no - "The Java programming language does not specify how soon a finalizer will be invoked" (JLS), "The finalize method might be called on a finalizable object only after an indefinite delay, if at all" (java.lang.Object)
  12. Can this resurrect during processing?

    • PR: no, and that's not bad
    • FZ: yes, officially supported

Links:

1

There are 1 best solutions below

0
On

Most of this has been addressed in Should Java 9 Cleaner be preferred to finalization? already. Since the Cleaner API builds on the PhantomReference, most of it applies as well to using PhantomReference directly.

In short, you are not supposed to replace finalize() usages with PhantomReference or Cleaner. For non-memory resources, you should prefer explicit closing right after using them, with the try-with-resources construct wherever feasible. The interaction with the garbage collector may act as a fallback to detect programming errors, but should not become the preferred way of resource cleanup.

In that regard, the ability to opt out the cleanup when the resource has been closed correctly, has significant relevance, as it will be the norm. You are underestimating the impact of it. Once, your class has a nontrivial finalizer, its objects require two garbage collection cycles, to get reclaimed, even when the finalize() immediately returns after checking a condition. This can make the difference between getting collected in a minor gc or being promoted to the old generation.

The most extreme example would be a shortly used resource represented by a purely local object whose memory allocation could get elided completely after Escape Analysis has been applied, whereas the presence of a nontrivial finalize() method invariably implies a global escape which prevents this optimization (amongst others, like lock elimination).

While the Cleaner API does start a dedicated thread, there is no requirement to do so when using PhantomReference. You could as well poll the queue right when the resource is used or a new one is about to be allocated. That doesn’t guaranty fast release of the resource (which gc triggered cleanup doesn’t guaranty anyway), but you’re ensuring that an allocation doesn’t fail while a collected object unnecessarily holds resources.

Even if you use dedicated threads for the cleanup, there’s a fundamental difference between starting threads under your control and having finalizers invoked by unspecified JVM threads outside your control, where a faulty finalize() method of another library could block the thread needed for your cleanup. The JVM may invoke multiple finalizer concurrently whereas you can decide how many threads you use for your PhantomReference based cleanup.