Object Reclamation in the JVM
Reachability Analysis
The JVM uses the reachability analysis algorithm to determine which objects can be garbage collected. This approach identifies a set of root objects that are guaranteed to be alive, then traces references to find all objects reachable from these roots. Any object not reachable from a GC root becomes eligible for collection.
GC Root Categories
Objects that serve as GC roots include:
- Local variables in JVM stack frames
- Method parameters and temporary variables on call stacks
- Static fields from the method area
- String constant pool references
- JNI handles from native method stacks
- Objects synchronized with the
synchronizedkeyword - JVM internal objects (classloaders, exception objects)
- JMX and JVMTI callbacks
You can analyze heap dumps using jmap:
jmap -dump:format=b,live,file=heap.bin <pid>
Java Reference Type Hierarchy
Strong Reference
The default reference type in Java. Any object reachable through a chain of strong references from a GC root will never be collected. This is what most Java code uses:
Object strong = new Object();
Soft Reference (SoftReference)
Objects with only soft references remain in memory until the JVM determines memory is insufficient. When allocation fails, the garbage collector will reclaim soft-referenced objects before throwing OutOfMemoryError.
Key characteristic: Collected only when memory pressure exists.
Weak Reference (WeakReference)
Weak references do not protect objects from collection. Whenever the garbage collector runs, weak-referenced objects are reclaimed regardless of memory availability.
Key characteristic: Collected on every GC cycle.
Phantom Reference (PhantomReference)
Phantom references must be paired with a ReferenceQueue. The referenced object cannot be accessed via get() - it always returns null. When an object is collected, the phantom reference is enqueued, allowing cleanup actions.
Common use case: Coordinating cleanup of off-heap memory like direct ByteBuffers.
Finalizer Reference (FinalReference)
Used internally for the finalize() mechanism. The Finalizer thread invokes finalize() on objects, but this happens during a second GC pass. Due to low thread priority, finalization is unreliable.
Reference Type Comparison
| Reference Type | GC Behavior | Common Usage |
|---|---|---|
| Strong | Never collected while referenced | Default |
| Soft | Collected when memory is low | Memory-sensitive caches |
| Weak | Collected on every GC | Canonicalized mappings |
| Phantom | Always returns null | Direct memory cleanup |
| Finalizer | Second GC pass after finalize | Legacy cleanup (avoid) |
Practical Application: Soft Reference for Large Cache
When caching large objects that should be released under memory pressure, soft references provide automatic memory management:
import java.lang.ref.SoftReference;
import java.util.ArrayList;
import java.util.List;
public class CacheDemo {
private static final int UNIT = 4 * 1024 * 1024;
public static void main(String[] args) {
List<SoftReference<byte[]>> cache = new ArrayList<>();
for (int i = 0; i < 5; i++) {
SoftReference<byte[]> ref = new SoftReference<>(new byte[UNIT]);
System.out.println("Created entry #" + (i + 1));
cache.add(ref);
}
System.out.println("Cache size: " + cache.size());
for (SoftReference<byte[]> ref : cache) {
System.out.println("Entry: " + ref.get());
}
}
}
Run with limited heap: -Xmx20m
The output shows that only the last entry survives when memory becomes insufficient:
Created entry #1
Created entry #2
Created entry #3
[GC collections...]
Created entry #4
[Full GC...]
Created entry #5
Cache size: 5
null
null
null
null
[B@7ea987ac
Using Reference Queues for Cleanup
When reference objects themselves need cleanup, associate them with a ReferenceQueue:
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.util.ArrayList;
import java.util.List;
public class QueueDemo {
private static final int UNIT = 4 * 1024 * 1024;
public static void main(String[] args) {
ReferenceQueue<byte[]> queue = new ReferenceQueue<>();
List<SoftReference<byte[]>> activeRefs = new ArrayList<>();
for (int i = 0; i < 5; i++) {
SoftReference<byte[]> ref = new SoftReference<>(
new byte[UNIT], queue
);
activeRefs.add(ref);
}
// Remove references that have been enqueued
Reference<? extends byte[]> cleaned;
while ((cleaned = queue.poll()) != null) {
activeRefs.remove(cleaned);
}
System.out.println("Active references: " + activeRefs.size());
}
}
Weak Reference in Cache Scenarios
Weak references suit scenarios where data can be reconstructed if discarded:
import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.Map;
public class WeakCache<K, V> {
private final Map<K, WeakReference<V>> cache = new HashMap<>();
public V get(K key) {
WeakReference<V> ref = cache.get(key);
return ref != null ? ref.get() : null;
}
public void put(K key, V value) {
cache.put(key, new WeakReference<>(value));
}
}
This implementation automatically discards entries when the JVM needs memory, allowing the garbage collector to reclaim the values.
Summary
Java provides five reference types offering different garbage collection behaviors. Strong references are the default and keep objects alive indefinitely. Soft references survive until memory pressure forces collection, making them suitable for memory-sensitive caches. Weak references allow immediate collection on any GC cycle, ideal for canonicalization maps. Phantom references enable post-collection cleanup coordination, particularly for off-heap resources. Finalizer references exist for backward compatibility but should be avoided due to unreliable timing.