patternjavaMinor
Resource caching in Java with soft references
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
ResourceLoader.java
I've also written the test:
```
import static
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
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
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:
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.
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.