1

When object with non-trivial finalize() method is created, JVM will create Finalizer (FinalReference) with this object as referent. What will happen if this object will be also wrapped by Soft/Weak or Phantom Reference? Would GC try to enqueue the Finalizer (call finalize method on it) at first, and then enqueue the other Reference or the opposite?

Martin
  • 129
  • 1
  • 6
  • 1
    You should not use `finalize()`. There are lots of questions+answers about this – [here's one](https://stackoverflow.com/questions/2506488/when-is-the-finalize-method-called-in-java) – and coverage in books (like Essential Java by Joshua Bloch). – Kaan Oct 03 '19 at 22:02
  • 1
    You are right, but this question is for better understanding GC behavior, not because I would like to use `finalize()`. – Martin Oct 04 '19 at 05:38

1 Answers1

2

I think, your question is not about the time of enqueuing, actually.

Consider the Notification section of the package documentation

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, it will add the reference to the associated queue.

(Note the “some time after”)

and likewise, all reference types have a statement of the form:

Suppose that the garbage collector determines at a certain point in time that an object is weakly reachable. At that time it will atomically clear all weak references to that object and all weak references to any other weakly-reachable objects from which that object is reachable through a chain of strong and soft references. At the same time it will declare all of the formerly weakly-reachable objects to be finalizable. At the same time or at some later time it will enqueue those newly-cleared weak references that are registered with reference queues.

(taken from the WeakReference; Note the “at the same time or at some later time”)

In practice, the garbage collector hands over the discovered references to another thread which does the enqueuing asynchronously. Since an unspecified delay makes the order in which an application will retrieve the references from a queue undeterministic, it’s pointless to ask about an order here.

However, I suppose, you’re actually interested in the other “certain point in time”, when “the garbage collector determines that the reachability of the referent has changed to the value corresponding to the type of the reference” and will decide to clear the references atomically and make them eligible for enqueuing.

When there is a mixture of multiple differently typed reference objects, including a Finalizer reference, but no strong reference, there are two possible scenarios:

  1. There is at least one soft reference and there is no memory pressure. Then, the garbage collector may decide not clear resp. enqueue any reference.
  2. There is no soft reference or the garbage collector decides that memory pressure justifies clearing soft references. Then all soft and weak references are cleared and all soft, weak, and finalizer references are handed over for enqueuing.
    Only phantom references stay untouched.

Note that once finalization started, there is no Finalizer reference anymore, but during finalization, new soft or weak reference might get created. So the resulting scenarios are the same as with the optimized handling of objects having a trivial finalize() method. There can be a mixture of soft, weak, and phantom references, without a Finalizer reference. When there is no remaining strong reference, we again have the two possible scenarios:

  1. There is at least one soft reference and there is no memory pressure. Then, the garbage collector may decide not clear resp. enqueue any reference.
  2. There is no soft reference or the garbage collector decides that memory pressure justifies clearing soft references. Then all soft, weak, and phantom references are clearedA and all soft, weak, and phantom references are handed over for enqueuing.

A phantom references are cleared in Java 9 or newer. In previous versions they are only enqueued without being cleared.

Community
  • 1
  • 1
Holger
  • 285,553
  • 42
  • 434
  • 765
  • Thank you for such a great answer. One thing is really interesting - a ```Finalizer``` reference object successively block ```Phantom``` reference object from being enqueued. So an object with mixture of ```Finalizer``` and ```Phantom``` reference will require **at least** 3 GC cycles (first - put to finalizer queue, second - put to phantom queue, third - remove from heap) for to be removed from heap. Am I right? – Martin Oct 23 '19 at 19:53
  • 1
    Yes, before Java 9, that was the case. Since Java 9, phantom references are cleared before enqueued, which allows reclaiming of the referent’s memory right at this point, so it can be done in two cycles. – Holger Oct 24 '19 at 06:43
  • Well, are you sure about those **two cycles**? As I remember reference processing happen in ```Remark``` phase, so let's consider Old Object with mixture of ```Finalizer``` and ```Phantom``` reference. GC will: 1st cycle - enqueue ```Final``` reference 2nd cycle - clear and enqueue ```Phantom``` reference (during ```Remark``` phase) 3rd cycle - (probably ```Mixed``` GC cycle) reclaim memory. – Martin Oct 24 '19 at 17:07
  • 1
    Perhaps, you have a very specific gc algorithm in mind. Generally, “reclaim memory” is not an actual action. Memory becomes available as a whole, when all survivor objects have been relocated. There is no need to treat the referent of a cleared phantom reference as survivor. So its memory gets reclaimed when it is not copied. “It can be done in two cycles”, doesn’t imply that all algorithms do. Especially concurrent collectors are…special. – Holger Oct 24 '19 at 17:20
  • Yes, I just assumed that we are speaking about G1 GC which has reference processing (of the Old gen) in ```Remark``` phase, so in that case GC will need at last 3 GC cycles (2 ```Concurrent Mark``` cycles, and 1 ```Mixed GC``` cycle) to reclaim memory. Anyway, thank you for such a good answers! – Martin Oct 24 '19 at 20:27
  • @Martin I have not looked too deep into logs, but a fast test shows : `GC(0) Pause Young (Normal) (G1 Evacuation Pause)` followed by `GC(0) Skipped phase1 of Reference Processing...`, the fact that there are no weak references to process in my program is irrelevant. The point is that : these logs appear under a `young pause`, where references _are_ processed also, not just `Remark`. As a matter of fact, they should be processed anywhere there is a phase that is paused. Some GCs (`Shenandoah`) can handle a lot of processing in the _concurrent_ phase also, – Eugene Mar 11 '20 at 22:57
  • mainly because it scans the `referent`, and not the `reference` (it's a bit more complicated, but the idea is this). Because of this, it can handle some types of weak references in a (under some conditions) in the _concurrent_ phase. – Eugene Mar 11 '20 at 22:59