HiveBrain v1.2.0
Get Started
← Back to all entries
patternjavaMinor

Resource caching in Java with soft references

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
resourcewithcachingjavareferencessoft

Problem

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 implements Supplier, AutoCloseable {

  private final ByteSource source;
  private final ResourceLoader loader;
  private SoftReference reference;

  Resource(ByteSource source, ResourceLoader loader) {
    this.source = source;
    this.loader = loader;
    reference = new SoftReference<>(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<>(null);
    }
  }
}


ResourceLoader.java

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

public interface ResourceLoader {

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

  public  T load(ByteSource source) throws IOException;
}


I've also written the test:

```
import static

Solution

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 "forget" 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 "extends" 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.

Context

StackExchange Code Review Q#91157, answer score: 3

Revisions (0)

No revisions yet.