Fundamentals 15 min read

When Does ZGC Actually Clear SoftReferences? Inside the JVM Reference Policy

This article explains how ZGC in OpenJDK 17 decides whether to reclaim SoftReference objects, detailing the unconditional clearing scenarios, the policies governing SoftReference lifetimes, and how the JVM quantifies memory pressure using heap size and the -XX:SoftRefLRUPolicyMSPerMB flag.

Bin's Tech Cabin
Bin's Tech Cabin
Bin's Tech Cabin
When Does ZGC Actually Clear SoftReferences? Inside the JVM Reference Policy

Based on OpenJDK 17 with ZGC as the garbage collector, this article extracts and expands topics from the previous piece "Using ZGC to Discuss How the JVM Implements Reference Semantics".

The description of SoftReference in many tutorials is oversimplified; this article examines the precise JVM implementation.

Two key questions are raised:

Does a SoftReference’s referent always survive when memory is sufficient?

How does the JVM quantify "insufficient memory" and decide when to reclaim the referent?

1. JVM Unconditional SoftReference Reclamation Scenarios

The core logic for handling Reference objects during ZGC’s ZReferenceProcessor phase is encapsulated in the ZReferenceProcessor class:

class ZReferenceProcessor : public ReferenceDiscoverer {
  // SoftReference handling policy
  ReferencePolicy* _soft_reference_policy;
}

The policy is stored in the private member _soft_reference_policy, whose initialization reveals the answers to the two questions.

When ZGC starts, it creates a ZDriverGCScope object, which prepares GC statistics, thread counts, and initializes the SoftReference policy:

void ZDriver::gc(const ZDriverRequest& request) {
  ZDriverGCScope scope(request);
  // ... omitted ...
}
class ZDriverGCScope : public StackObj {
private:
  GCCause::Cause _gc_cause;
public:
  ZDriverGCScope(const ZDriverRequest& request)
    : _gc_cause(request.cause()) {
    const bool clear = should_clear_soft_references(request);
    ZHeap::heap()->set_soft_reference_policy(clear);
  }
};

The method should_clear_soft_references determines whether ZGC must unconditionally clear SoftReferences:

static bool should_clear_soft_references(const ZDriverRequest& request) {
  // Clear soft references if implied by the GC cause
  if (request.cause() == GCCause::_wb_full_gc ||
      request.cause() == GCCause::_metadata_GC_clear_soft_refs ||
      request.cause() == GCCause::_z_allocation_stall) {
    // Unconditional clear of SoftReference
    return true;
  }
  // Don't clear
  return false;
}

Thus, ZGC unconditionally clears SoftReferences when any of the following GC causes occur:

The cause is _wb_full_gc, triggered by WhiteBox Full GC.

The cause is _metadata_GC_clear_soft_refs, a Full GC caused by metadata allocation failure.

The cause is _z_allocation_stall, a GC triggered when ZGC’s blocking page allocation cannot obtain memory.

If memory is sufficient, ZGC uses the lru_max_heap_policy to decide SoftReference lifetimes; otherwise it adopts always_clear_policy to clear them immediately.

2. How the JVM Quantifies Memory Shortage

The LRUMaxHeapPolicy::setup() method computes the maximum survival interval for a SoftReference based on the remaining heap size:

void LRUMaxHeapPolicy::setup() {
  size_t max_heap = MaxHeapSize;
  // Subtract heap usage after the last GC
  max_heap -= Universe::heap()->used_at_last_gc();
  // Convert to MB
  max_heap /= M;
  // -XX:SoftRefLRUPolicyMSPerMB defaults to 1000 ms per MB
  _max_interval = max_heap * SoftRefLRUPolicyMSPerMB;
  assert(_max_interval >= 0, "Sanity check");
}

The JVM obtains the maximum heap from the -Xmx setting, subtracts the used space recorded after the previous GC, and converts the result to megabytes. The interval is then multiplied by the JVM flag -XX:SoftRefLRUPolicyMSPerMB (default 1000 ms per MB) to obtain _max_interval.

SoftReference contains two crucial fields: clock: a global timestamp updated by the JVM after each GC. timestamp: the last GC time when the SoftReference was accessed via get().

After each GC, ZGC calls soft_reference_update_clock() to refresh SoftReference::clock:

static void soft_reference_update_clock() {
  const jlong now = os::javaTimeNanos() / NANOSECS_PER_MILLISEC;
  java_lang_ref_SoftReference::set_clock(now);
}

When an application thread calls SoftReference.get(), the current clock value is stored into timestamp, keeping them equal as long as the reference is frequently accessed.

SoftReference clock update diagram
SoftReference clock update diagram

If a SoftReference has not been accessed for a long time, its timestamp lags behind the global clock, indicating many GC cycles have passed.

SoftReference timestamp lag diagram
SoftReference timestamp lag diagram

During the ZGC Concurrent Mark phase, the JVM evaluates each Reference via should_discover. For SoftReferences, it delegates to is_softly_live, which consults the current policy:

bool ZReferenceProcessor::should_discover(oop reference, ReferenceType type) const {
  if (is_inactive(reference, referent, type)) return false;
  if (is_strongly_live(referent)) return false;
  if (is_softly_live(reference, type)) return false;
  return true;
}
bool ZReferenceProcessor::is_softly_live(oop reference, ReferenceType type) const {
  if (type != REF_SOFT) return false;
  const jlong clock = java_lang_ref_SoftReference::clock();
  return !_soft_reference_policy->should_clear_reference(reference, clock);
}

The policy’s should_clear_reference compares the interval between clock and the SoftReference’s timestamp with _max_interval:

bool LRUMaxHeapPolicy::should_clear_reference(oop p, jlong timestamp_clock) {
  jlong interval = timestamp_clock - java_lang_ref_SoftReference::timestamp(p);
  if (interval <= _max_interval) {
    // The referent survives this GC
    return false;
  }
  // Interval exceeds the allowed maximum; the referent is reclaimed
  return true;
}

Consequently, a SoftReference is reclaimed only when the time since its last access exceeds _max_interval, which is determined by the remaining heap size and the -XX:SoftRefLRUPolicyMSPerMB setting.

Summary

The precise moment ZGC clears a SoftReference is when the object has not been accessed for longer than the maximum interval ( _max_interval ) computed from the current free heap space and the -XX:SoftRefLRUPolicyMSPerMB parameter.

For example, if the free heap is 1 MB and -XX:SoftRefLRUPolicyMSPerMB=1000, a SoftReference that remains untouched for more than 1000 ms will be reclaimed during the next GC.

JVMGarbage CollectionzgcSoftReferenceReference Policy
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.