Cache Eviction Strategies and Java Cache Implementations
This article explains various cache eviction strategies, compares heap, off‑heap, disk and distributed cache types, and provides concrete Java implementations using Guava Cache, EhCache 3.x and MapDB with code examples and usage patterns such as Cache‑Aside and Cache‑As‑SoR.
1. Cache Eviction Strategies
Cache can be reclaimed based on space (e.g., limit to 10 MB), capacity (maximum number of entries), time (TTL – time‑to‑live, TTI – time‑to‑idle), or Java object references (soft and weak references). Common algorithms include FIFO (first‑in‑first‑out), LRU (least‑recently‑used) and LFU (least‑frequently‑used). Soft references allow the JVM to reclaim objects when memory is low, while weak references are reclaimed as soon as they are not strongly reachable.
2. Java Cache Types
Four main cache storage locations are discussed:
Heap cache – stores objects in the JVM heap; fast but can increase GC pause time.
Off‑heap (outside‑heap) cache – stores data in native memory, reducing GC impact and allowing larger capacities.
Disk cache – persists data on disk, surviving JVM restarts.
Distributed cache – shared across multiple JVMs, solving single‑node capacity and consistency issues (e.g., Redis, EhCache clustered with Terracotta).
3. Java Cache Implementations
3.1 Guava Cache (heap only)
Guava provides a lightweight, high‑performance heap cache.
Cache<String, String> myCache =
CacheBuilder.newBuilder()
.concurrencyLevel(4)
.expireAfterWrite(10, TimeUnit.SECONDS)
.maximumSize(10000)
.build();Configuration options include maximumSize (capacity‑based eviction), expireAfterWrite (TTL), expireAfterAccess (TTI), weakKeys/weakValues, softValues, invalidate methods, concurrencyLevel, and recordStats for hit‑rate statistics.
3.2 EhCache 3.x (supports heap, off‑heap, disk, and distributed)
CacheManager cacheManager = CacheManagerBuilder.newCacheManagerBuilder().build(true);
CacheConfigurationBuilder<String, String> cacheConfig =
CacheConfigurationBuilder.newCacheConfigurationBuilder(
String.class, String.class,
ResourcePoolsBuilder.newResourcePoolsBuilder()
.heap(100, EntryUnit.ENTRIES))
.withDispatcherConcurrency(4)
.withExpiry(Expirations.timeToLiveExpiration(Duration.of(10, TimeUnit.SECONDS)));
Cache<String, String> myCache = cacheManager.createCache("myCache", cacheConfig);EhCache supports capacity‑based, space‑based, and time‑based eviction, as well as explicit invalidate/remove operations. Concurrency is handled internally (default 16) and can be tuned via withDispatcherConcurrency.
3.3 MapDB 3.x (heap, off‑heap, and disk options)
HTreeMap myCache = DBMaker.heapDB()
.concurrencyScale(16)
.make()
.hashMap("myCache")
.expireMaxSize(10000)
.expireAfterCreate(10, TimeUnit.SECONDS)
.expireAfterUpdate(10, TimeUnit.SECONDS)
.expireAfterGet(10, TimeUnit.SECONDS)
.create();MapDB offers similar eviction controls and can be configured for off‑heap or memory‑mapped disk storage.
4. Cache Usage Patterns
4.1 Cache‑Aside – application code explicitly reads/writes the cache and falls back to the system‑of‑record (SoR) when needed.
// Read example
value = myCache.getIfPresent(key);
if (value == null) {
value = loadFromSoR(key);
myCache.put(key, value);
}
// Write example
writeToSoR(key, value);
myCache.put(key, value);4.2 Cache‑As‑SoR – the cache is treated as the primary data store; a CacheLoader (read‑through) or CacheWriter (write‑through/write‑behind) handles interaction with the underlying SoR.
Read‑through example (Guava):
LoadingCache<Integer, Result<Category>> cache =
CacheBuilder.newBuilder()
.softValues()
.maximumSize(5000)
.expireAfterWrite(2, TimeUnit.MINUTES)
.build(new CacheLoader<Integer, Result<Category>>() {
@Override
public Result<Category> load(Integer id) throws Exception {
return categoryService.get(id);
}
});Write‑through example (EhCache):
CacheManager cm = CacheManagerBuilder.newCacheManagerBuilder().build(true);
Cache<String, String> cache = cm.createCache("myCache",
CacheConfigurationBuilder.newCacheConfigurationBuilder(String.class, String.class,
ResourcePoolsBuilder.newResourcePoolsBuilder().heap(100, MemoryUnit.MB))
.withLoaderWriter(new DefaultCacheLoaderWriter<String, String>() {
@Override
public void write(String key, String value) throws Exception {
// write to SoR
}
@Override
public void delete(String key) throws Exception {
// delete from SoR
}
}));Write‑behind (asynchronous) can also be achieved with EhCache’s CacheWriter, allowing batch or delayed writes to the SoR.
4.3 Copy Patterns – Copy‑On‑Read and Copy‑On‑Write protect against unintended modifications when caches store mutable objects. EhCache provides support for these patterns via a Copier interface.
Architecture Digest
Focusing on Java backend development, covering application architecture from top-tier internet companies (high availability, high performance, high stability), big data, machine learning, Java architecture, and other popular fields.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.