0

I want to do something like this:

 CacheBuilder
            .newBuilder()
            .maximumSize(CONFIG.cacheMaxSize())
            .expireAfterAccess(CONFIG.cacheTimeout(),
                                CONFIG.cacheTimeUnit())
            .weakValues()
            .build(cacheLoader);

The behavior I expect is that an entry will only be expired if the value is not referenced AND the expiration time has passed. Is that how this usage will work?

jacob
  • 2,762
  • 1
  • 20
  • 49

2 Answers2

3

Not directly, since the weak value can be garbage collected as soon as there are no more strong references to the object. What you could do however is use a ForwardingCache backed by two separate caches, a weak-value cache and a timed-expiry cache, so that the time-based cache holds a strong reference to the object thereby keeping it in the weak-value cache. It'd look something like this:

public class WeakValuedExpiringCache<K, V> extends ForwardingCache<K, V> {
  private final Cache<K, V> expiringCache;
  private final Cache<K, V> weakCache;

  public WeakValuedExpiringCache(CacheBuilder expiringSpec) {
    expiringCache = expiringSpec.build();
    weakCache = CacheBuilder.newBuilder().weakValues().build();
  }

  // weakCache is the canonical cache since it will hold values longer than
  // expiration if there remain other strong references
  protected Cache<K, V> delagate() {
    return weakCache;
  }

  @override
  public V get(K key, Callable<? extends V> valueLoader)
     throws ExecutionException {
    // repopulate the expiring cache if needed, and update the weak cache
    V value = expiringCache.get(key, valueLoader);
    weakCache.put(key, value); // don't call super.put() here
  }

  @Override
  public void put(K key, V value) {
    expiringCache.put(key, value);
    super.put(key, value);
  }

  // Handle putAll(), cleanUp(), invalidate(), and invalidateAll() similarly
}

You can do the same thing with a ForwardingLoadingCache as well, just like .get() above you should load the value from the expiringCache and .put() it into the weakCache in the relevant loading methods.

dimo414
  • 47,227
  • 18
  • 148
  • 244
  • Thank you! That's perfect. And you saw into my question that I meant that there should be no other references to the value BESIDES the cache. I hadn't thought through that a weak reference won't ever prevent gc. – jacob Jun 01 '16 at 01:33
1

No, an entry will expire if the value is not referenced OR the expiration time has passed:

public class CacheBuilderIT {
    @Test
    public void expireAfterAccessWithWeakValues() throws InterruptedException {
        Cache<Object, Object> cache = CacheBuilder.newBuilder()
                .expireAfterAccess(500, MILLISECONDS)
                .weakValues()
                .build();
        Object key = new Object();
        Object value = new Object(); // keep a strong reference to the value
        cache.put(key, value);
        Thread.sleep(300);
        assert cache.getIfPresent(key) != null : "expiration occurred too quickly";
        Thread.sleep(300);
        assert cache.getIfPresent(key) != null : "last access did not reset expiration";
        Thread.sleep(1000);
        assert cache.getIfPresent(key) != null : "reference did not prevent expiration";
    }
}

Ouptut:

java.lang.AssertionError: reference did not prevent expiration
mfulton26
  • 29,956
  • 6
  • 64
  • 88