杰瑞科技汇

Java WeakReference,何时会被自动回收?

Of course! Let's dive deep into java.lang.ref.WeakReference. It's a fundamental concept in Java for memory management, especially when dealing with caches.

Java WeakReference,何时会被自动回收?-图1
(图片来源网络,侵删)

What is a WeakReference?

In Java, a WeakReference is a reference to an object that does not prevent the object from being garbage collected.

This is the key difference from a standard (strong) reference. When you create a strong reference to an object (e.g., MyObject obj = new MyObject();), that object is guaranteed to stay in memory as long as that reference exists. The Garbage Collector (GC) will not reclaim it.

A WeakReference, on the other hand, is like a "suggestion" to the Garbage Collector. It says, "I know about this object, but if memory is tight, feel free to discard it."

The Reference Hierarchy

To understand WeakReference, it's helpful to see it in the context of Java's reference strength hierarchy, from strongest to weakest:

Java WeakReference,何时会被自动回收?-图2
(图片来源网络,侵删)
  1. Strong Reference (The default)

    • Example: Object obj = new Object();
    • Behavior: If an object has at least one strong reference to it, it is not eligible for garbage collection. This is the standard way we use objects in Java.
  2. SoftReference

    • Behavior: An object with a soft reference is eligible for garbage collection, but the GC will generally try to avoid reclaiming it. It's typically used for memory-sensitive caches. The GC will only reclaim softly referenced objects when it's running low on memory, and even then, it may reclaim them before throwing an OutOfMemoryError.
  3. WeakReference

    • Behavior: An object with a weak reference is immediately eligible for garbage collection. The GC is free to reclaim it at any time. The only guarantee is that it will be reclaimed before an OutOfMemoryError is thrown. This makes it perfect for canonicalizing or caching data that can be cheaply recomputed.
  4. PhantomReference

    Java WeakReference,何时会被自动回收?-图3
    (图片来源网络,侵删)
    • Behavior: The most "ghost-like" reference. It doesn't allow you to retrieve the object itself (get() always returns null). Its sole purpose is to be enqueued in a ReferenceQueue after the object has been finalized and is about to be reclaimed. This is used for very advanced cleanup operations.

How WeakReference Works: The Lifecycle

Let's walk through the lifecycle of an object with a weak reference.

  1. Creation:

    Object strongRef = new Object(); // A strong reference to the object
    WeakReference<Object> weakRef = new WeakReference<>(strongRef); // A weak reference to the same object

    At this point, the object is strongly referenced by strongRef and weakly referenced by weakRef. It is not eligible for GC.

  2. Removing the Strong Reference:

    strongRef = null; // Now the object only has a weak reference to it

    Now, the object is only reachable via the WeakReference. It is eligible for garbage collection. However, it will not be collected immediately. The GC runs at its own discretion.

  3. Before Garbage Collection:

    • If you call weakRef.get(), it will return the actual object.
    • The object is alive and can be used.
  4. After Garbage Collection:

    • The GC runs, finds the object is only weakly reachable, and reclaims it.
    • If you now call weakRef.get(), it will return null.

    This is the critical check: You must always check if get() returns null before using the object.


Code Example: A Simple Demonstration

This example shows the core behavior. We'll use System.gc() to hint to the Garbage Collector to run, which is good for demonstration but generally not needed in production code.

import java.lang.ref.WeakReference;
public class WeakReferenceDemo {
    public static void main(String[] args) {
        // 1. Create an object and a strong reference to it
        Object myObject = new Object();
        System.out.println("myObject created: " + myObject);
        // 2. Create a weak reference to the object
        WeakReference<Object> weakRef = new WeakReference<>(myObject);
        System.out.println("WeakReference created. Can we get the object? " + weakRef.get());
        // 3. Remove the strong reference
        myObject = null;
        System.out.println("Strong reference set to null. Object is now only weakly reachable.");
        System.out.println("Can we still get the object from weakRef? " + weakRef.get());
        // 4. Suggest the Garbage Collector to run
        System.gc();
        System.out.println("Garbage Collector hinted to run.");
        // 5. Check again after a potential GC cycle
        // It's good practice to wait a moment for the GC to complete
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("After GC, can we get the object? " + weakRef.get());
    }
}

Typical Output:

myObject created: java.lang.Object@15db9742
WeakReference created. Can we get the object? java.lang.Object@15db9742
Strong reference set to null. Object is now only weakly reachable.
Can we still get the object from weakRef? java.lang.Object@15db9742
Garbage Collector hinted to run.
After GC, can we get the object? null

As you can see, after the strong reference was removed and the GC ran, weakRef.get() returned null, indicating the object had been collected.


Why Use WeakReference? The Classic Use Case: Caches

The most common use for WeakReference is to build a cache that doesn't prevent its contents from being garbage collected. This prevents the cache from causing an OutOfMemoryError.

Imagine a simple cache for user profiles:

import java.util.HashMap;
import java.util.Map;
import java.lang.ref.WeakReference;
public class UserCache {
    // The map stores WeakReferences to User objects
    private final Map<String, WeakReference<User>> cache = new HashMap<>();
    public void put(String userId, User user) {
        cache.put(userId, new WeakReference<>(user));
    }
    public User get(String userId) {
        WeakReference<User> weakRef = cache.get(userId);
        // Always check if the reference is still valid!
        if (weakRef != null) {
            return weakRef.get();
        }
        return null;
    }
    // A simple User class
    static class User {
        String name;
        public User(String name) { this.name = name; }
    }
}

How this cache works:

  1. You add users to the cache: cache.put("user1", new User("Alice"));.
  2. The User object is now in the cache, held by a WeakReference.
  3. If your application runs out of memory and the GC runs, it can see that the User object is only referenced by this weak reference in the cache. It will reclaim the User object.
  4. Later, when you call cache.get("user1"), the get() method on the WeakReference will return null. Your application can then detect this and either:
    • Re-fetch the user's data from the database.
    • Do nothing if the user is no longer needed.

This creates a "self-cleaning" cache that never grows uncontrollably and prevents memory leaks.


The Role of ReferenceQueue

When the GC reclaims an object that has a WeakReference, it can optionally add that WeakReference object to a ReferenceQueue. This is a powerful way to be notified when your object has been collected.

Why is this useful? It allows you to clean up the cache automatically.

import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.Map;
import java.util.WeakHashMap;
// Note: Java already has a WeakHashMap that does this for you!
// This example is for educational purposes.
public class CacheWithQueue {
    private final Map<String, WeakReference<User>> cache = new HashMap<>();
    private final ReferenceQueue<User> queue = new ReferenceQueue<>();
    public void put(String userId, User user) {
        // The reference is added to the queue when the user is GC'd
        WeakReference<User> weakRef = new WeakReference<>(user, queue);
        cache.put(userId, weakRef);
    }
    public void cleanUp() {
        // Check the queue for references that have been GC'd
        Reference<? extends User> ref;
        while ((ref = queue.poll()) != null) {
            // The WeakReference itself is in the cache. Find it and remove it.
            // This requires a bit of work. A WeakHashMap handles this internally.
            // We can iterate and remove entries where value.equals
分享:
扫描分享到社交APP
上一篇
下一篇