TL;DR

  • Scenario: Guava Cache is widely used in production, but many lack source-level understanding of LocalCache/Segment/LoadingCache behavior
  • Conclusion: Guava achieves thread-safe local cache, expiration and refresh strategies through LocalCache+Segment structure, reference queues, and access/write queues
  • Output: An engineering-focused note covering class hierarchy, put/get flow and expiration reload mechanism

Class Hierarchy

Guava Cache core class structure:

  • CacheBuilder: Cache builder, specifies configuration parameters and initializes local cache
  • CacheLoader: Abstract class for loading data from data source
  • Cache: Interface defining get, put, invalidate operations
  • LoadingCache: Interface extending Cache, defining get, getUnchecked, getAll operations
  • LocalCache: Core class containing data structure and basic cache operation methods
  • LocalManualCache: LocalCache inner static class implementing Cache interface
  • LocalLoadingCache: LocalCache inner static class extending LocalManualCache implementing LoadingCache interface

LocalCache Core Structure

LocalCache is Guava Cache’s core class. Its data structure is similar to ConcurrentHashMap, consisting of multiple Segments:

Main Fields:

  • Segment<K, V>[] segments: Map array
  • int concurrencyLevel: Concurrency level, i.e., segments array size
  • long expireAfterAccessNanos: Time to expire after access
  • long expireAfterWriteNanos: Time to expire after write
  • long refreshNanos: Refresh time

Segment Composition:

  • AtomicReferenceArray<ReferenceEntry<K, V>> table: Hash table
  • Queue<ReferenceEntry<K, V>> writeQueue: Write order queue
  • Queue<ReferenceEntry<K, V>> accessQueue: Access order queue

Put Flow Analysis

  1. Locking: Lock ensures thread safety of put operations within current Segment
  2. Cleanup Queue Elements: Clean up keyReferenceQueue and valueReferenceQueue
  3. setValue: Write value to Entry
V put(K key, int hash, V value, boolean onlyIfAbsent) {
    lock();
    try {
        long now = map.ticker.read();
        preWriteCleanup(now);
    } finally {
        unlock();
        postWriteCleanup();
    }
}

Get Flow Analysis

  1. Get object reference (may be non-alive state)
  2. Check if object reference is alive
  3. If alive and refresh is set, query value with async refresh
  4. For non-alive but loading, wait for loading to complete
  5. If value not yet obtained, query loader method to get corresponding value
V get(K key, CacheLoader<? super K, V> loader) throws ExecutionException {
    int hash = hash(checkNotNull(key));
    return segmentFor(hash).get(key, hash, loader);
}

Expiration Reload Mechanism

Data expiration does not automatically reload, but executes expiration reload through get operation.

LocalLoadingCache Core Methods:

  • get(K key): Load via CacheLoader if not exists
  • getUnchecked(K key): No checked exception declaration
  • getAll(Iterable<? extends K> keys): Batch loading
  • refresh(K key): Active refresh

Error Quick Reference

SymptomRoot CauseFix
Occasionally read old data from cacheOnly configured expireAfterWrite/Access, relying on lazy expirationConfigure refreshAfterWrite or explicit refresh
size() significantly fluctuates under high concurrencySegment.count + modCount designed as weakly consistent snapshotAvoid using size() for capacity/rate limiting
Occasional long-tail requests during QPS spikeCacheLoader execution takes long timeOptimize data source query or introduce local merge loading
JVM heap usage continuously increasesNot set maximumSize/maximumWeightExplicitly set capacity and weight strategy