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 arrayint concurrencyLevel: Concurrency level, i.e., segments array sizelong expireAfterAccessNanos: Time to expire after accesslong expireAfterWriteNanos: Time to expire after writelong refreshNanos: Refresh time
Segment Composition:
AtomicReferenceArray<ReferenceEntry<K, V>> table: Hash tableQueue<ReferenceEntry<K, V>> writeQueue: Write order queueQueue<ReferenceEntry<K, V>> accessQueue: Access order queue
Put Flow Analysis
- Locking: Lock ensures thread safety of put operations within current Segment
- Cleanup Queue Elements: Clean up keyReferenceQueue and valueReferenceQueue
- 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
- Get object reference (may be non-alive state)
- Check if object reference is alive
- If alive and refresh is set, query value with async refresh
- For non-alive but loading, wait for loading to complete
- 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 existsgetUnchecked(K key): No checked exception declarationgetAll(Iterable<? extends K> keys): Batch loadingrefresh(K key): Active refresh
Error Quick Reference
| Symptom | Root Cause | Fix |
|---|---|---|
| Occasionally read old data from cache | Only configured expireAfterWrite/Access, relying on lazy expiration | Configure refreshAfterWrite or explicit refresh |
| size() significantly fluctuates under high concurrency | Segment.count + modCount designed as weakly consistent snapshot | Avoid using size() for capacity/rate limiting |
| Occasional long-tail requests during QPS spike | CacheLoader execution takes long time | Optimize data source query or introduce local merge loading |
| JVM heap usage continuously increases | Not set maximumSize/maximumWeight | Explicitly set capacity and weight strategy |