Resource caching in Java with soft references I'd like to cache some heavy resources locally. The goal of this implementation is to have be able to load resources for an unknown amount of time and then keep them in memory and finally evict them upon needs. So there is no "time-based" evictions needed: only "memory-based" ones.

As written in comment, I'm writing this code in the scope of a small library using only Guava as dependency, and currently, Guava doesn't support the single-element cache.

I've developped the following code:

Resource.java

import com.google.common.io.ByteSource;
import java.io.UncheckedIOException;
import java.lang.ref.SoftReference;
import java.util.function.Supplier;

public class Resource&lt;T&gt; implements Supplier&lt;T&gt;, AutoCloseable {

  private final ByteSource source;
  private final ResourceLoader&lt;T&gt; loader;
  private SoftReference&lt;T&gt; reference;


  Resource(ByteSource source, ResourceLoader&lt;T&gt; loader) {
    this.source = source;
    this.loader = loader;
    reference = new SoftReference&lt;&gt;(null);
  }

  @Override
  public T get() throws UncheckedIOException {
    // Double-checked locking
    T object = reference.get();
    if (object == null) {
      synchronized(this) {
        object = reference.get();
        if (object == null) {
          object = this.loader.uncheckedLoad(this.source);
          this.reference = new SoftReference(object);
        }
      }
    }
    return object;
  }

  @Override
  public void close() {
    synchronized(this) {
      this.reference = new SoftReference&lt;&gt;(null);
    }
  }
}


ResourceLoader.java

import com.google.common.io.ByteSource;
import java.io.IOException;
import java.io.UncheckedIOException;

public interface ResourceLoader&lt;T&gt; {

  public default &lt;T&gt; T uncheckedLoad(ByteSource source) throws UncheckedIOException {
    try {
      return this.load(source);
    } catch (IOException ex) {
      throw new UncheckedIOException(ex);
    }
  }

  public &lt;T&gt; T load(ByteSource source) throws IOException;
}


I've also written the test:

import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.sameInstance;
import static org.junit.Assert.assertThat;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

public class ResourceTest {

  @org.junit.Test
  public void shouldCacheInstanceAndReturnIt() throws java.io.IOException {
    ResourceLoader&lt;String&gt; loader = mock(ResourceLoader.class);
    String abc = new String("abc"); // Force a specific address to avoid javac optimization
    when(loader.uncheckedLoad(any())).thenReturn(abc);
    when(loader.load(any())).thenReturn(abc);

    Resource&lt;String&gt; resource = new Resource(null, loader);

    assertThat(resource.get(), is(sameInstance(abc)));
    assertThat(resource.get(), is(sameInstance(abc)));

    verify(loader, times(1)).uncheckedLoad(any());
  }
}


My various questions regarding this code are the following:


Is this implementation appropriate for a cache?
Is the double-checked locking the right way to use this? A priori, various calls to Resource::get should return identical object (not especially in regard to Object::equals or ==, but mostly in regards to what the final user sees).
How can I properly test this implementation? I see it's working: my test passes, but how can I simulate a soft reference eviction? Can I simply call my close() method?
Double-checked
Double-checked locking does not work. There are some complicated reasons for this, but, it's enough to say that the Java specification allows the Java runtime to legitimately reorder some of the checking code in a way that makes the sync lock out-of-alignment with the checks you think you are checking. (See: Double checked locking pattern: Broken or not? )
AutoClosable
Now, about the AutoClosable.
Why?
What does that get for you? You forcefully &quot;forget&quot; a reference that was allowed to be garbage collected anyway. Additionally, it does not close the instance at all, it just makes it refresh later. The AutoClose is a shim, that's not a very useful one at that.
SoftReference
Here's a paradox with caching. What are you caching here? The instance of the resource that you read from the ResourceLoader, or the Resource class itself? It would be preferable to cache the Resource instance itself, but you cache the underlying data. Additionally, you re-load it if the cache is cleared by the GC.
My problem is that anything that has a strong reference to the Resource instance probably wants to find the content again at some point.... otherwise it would not need to hold the reference. Thus, if you have memory problems, it is possibly just better to reload the resource each time, than to possibly hang on to it.
Are you sure that you want a SoftReference?
If yes, why don't you have a ReferenceQueue? A reference queue allows you to back-reference a SoftReference to the Resource that owns it. It works as follows:

create a sub-class of SoftReference (not a wrapper, but an actual &quot;extends&quot; instance).
put a strong-reference back to the Resource that the SoftReference comes from
register the SoftReference on a queue

Then, in a separate thread, pull GC'd items off the queue, and process them. This allows you to get an ahead-of-time peek at data that has been de-cached.
It also allows you to have a colleciton of Resources that are held in, for example, a Map, and you can clear the Resources out of the map as they are cleared out by the GC.