Fundamentals 11 min read

Why PhantomReference Can Track GC While WeakReference Can’t in ZGC

Exploring the differences between PhantomReference and WeakReference in Java’s ZGC, this article explains how ZGC’s concurrent phases and the should_drop method use distinct bitmap bits to determine object liveness, revealing why only PhantomReference reliably tracks object reclamation in certain finalization scenarios.

Bin's Tech Cabin
Bin's Tech Cabin
Bin's Tech Cabin
Why PhantomReference Can Track GC While WeakReference Can’t in ZGC
This article discusses OpenJDK 17 with the ZGC garbage collector.

PhantomReference and WeakReference are both reference types in the JVM, but they behave differently when tracking object reclamation. A PhantomReference is enqueued in a ReferenceQueue after its referent is reclaimed, allowing applications to detect that the object has been collected and perform cleanup.

A WeakReference is cleared and enqueued when its referent becomes only weakly reachable (no strong or soft references). In most cases WeakReference can also be used to track reclamation, but there is a special scenario where it fails.

public class PhantomReference<T> extends Reference<T> {
    public PhantomReference(T referent, ReferenceQueue<? super T> q) {
        super(referent, q);
    }
}

When an object is simultaneously referenced by a WeakReference and a FinalReference (or PhantomReference) without any strong or soft references, the finalizer can resurrect the object, keeping it alive. In this situation the WeakReference is still enqueued, so it cannot reliably indicate that the object has been reclaimed, whereas the PhantomReference will not be enqueued until the next GC cycle after the object is truly dead.

In ZGC, the Concurrent Mark phase collects all Reference objects into a temporary _discovered_list. During the Concurrent Process Non-Strong References phase, the should_drop method checks whether each referent is still alive.

bool ZReferenceProcessor::should_drop(oop reference, ReferenceType type) const {
    const oop referent = reference_referent(reference);
    if (type == REF_PHANTOM) {
        return ZBarrier::is_alive_barrier_on_phantom_oop(referent);
    } else {
        return ZBarrier::is_alive_barrier_on_weak_oop(referent);
    }
}

The method uses different barrier checks: ZBarrier::is_alive_barrier_on_phantom_oop for PhantomReference and ZBarrier::is_alive_barrier_on_weak_oop for WeakReference. ZGC maintains a per‑page bitmap ( _livemap) where each object is represented by two bits: the low bit indicates resurrection by a FinalReference, and the high bit indicates strong liveness.

class ZPage : public CHeapObj<mtGC> {
private:
    ZLiveMap _livemap;
};

ZGC defines three page types:

Small (2 MiB, objects aligned to 8 B, max size 256 KiB)

Medium (≈32 MiB, objects aligned to 4 KiB, max size 4 MiB)

Large (variable size, aligned to 2 MiB, holds a single large object)

uintptr_t ZObjectAllocator::alloc_object(size_t size, ZAllocationFlags flags) {
    if (size <= ZObjectSizeLimitSmall) {
        return alloc_small_object(size, flags);
    } else if (size <= ZObjectSizeLimitMedium) {
        return alloc_medium_object(size, flags);
    } else {
        return alloc_large_object(size, flags);
    }
}

The bitmap size is twice the number of objects because ZGC needs two bits per object:

static size_t bitmap_size(uint32_t size, size_t nsegments) {
    return MAX2<size_t>(size, nsegments) * 2;
}

When an object is revived by a FinalReference, the low bit is set to 1. If the object also has a strong reference chain, both bits are set to 1 ( 11). During should_drop, a PhantomReference checks the low bit; if it is set (object revived), the reference is removed from _discovered_list. A WeakReference checks the high bit; it remains in the list regardless of finalization, so it will be processed later.

In summary, PhantomReference objects are only processed after the referent is truly reclaimed and can be affected by finalization, while WeakReference objects are processed as soon as the referent becomes weakly reachable, regardless of finalization.

JavaJVMGarbage CollectionzgcWeakReferencePhantomReference
Bin's Tech Cabin
Written by

Bin's Tech Cabin

Original articles dissecting source code and sharing personal tech insights. A modest space for serious discussion, free from noise and bureaucracy.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.