eviction-frequency

Least-frequently-used cache eviction

LeastFrequentlyUsed is an EvictionStrategy that, when an Insert exceeds its capacity, removes the key used least often, and among equals the one used longest ago.

Rationale

Why. Some keys are asked for all day, and a recency order forgets one after a single quiet spell.

Key Concepts

Frequency

Role. Each Insert and Hit adds one to its key's count. A Miss changes nothing, and an Evict discards the count.

Pattern. STRATEGY, OBSERVER

How It Works

Guarantees. No public parameter takes null and no public method returns it, declared with contrax's @PreCondition(IsNotNull.class) and @PostCondition(IsNotNull.class).

Thread safety. An instance is not thread-safe: events must reach update one at a time.

Lane

Purpose. Eviction for workloads with a stable set of popular keys.

Alternative. eviction-recency when popularity shifts.

When to avoid. Popularity that shifts over time: a once-popular key outlasts a newly popular one.

Usage

Room for two: a is read three times and stays, while b and then c leave

import java.time.Instant;
import java.util.Map;
import java.util.Optional;
import java.util.function.Supplier;
import java.util.stream.Stream;
import software.visionary.cache.Cache;
import software.visionary.cache.capacity.fixed.FixedSize;
import software.visionary.cache.events.CacheEvent;
import software.visionary.cache.events.Evict;
import software.visionary.cache.eviction.frequency.LeastFrequentlyUsed;
import software.visionary.cache.policy.CachePolicy;
import software.visionary.cache.topology.read.through.ReadThroughCacheFactory;
import software.visionary.events.Observer;

Map<String, Integer> source = Map.of("a", 1, "b", 2, "c", 3, "d", 4);
Supplier<Cache<String, Integer>> caches = new ReadThroughCacheFactory<>(
        key -> Optional.ofNullable(source.get(key)),
        new CachePolicy<>(() -> new FixedSize<>(2), LeastFrequentlyUsed::new, Instant::now));
Observer<CacheEvent<String, ?>> printEvictions = event -> {
    if (event instanceof Evict<String, ?> evicted) { System.out.println("evicted " + evicted.key()); }
};

var cache = caches.get();
cache.add(printEvictions);
Stream.of("a", "a", "a", "b", "c", "d").forEach(cache::apply);

Output

evicted b
evicted c

a and b are each used twice and b's last use is older, so b leaves

var tied = caches.get();
tied.add(printEvictions);
Stream.of("a", "b", "b", "a", "c").forEach(tied::apply);

Output

evicted b

Dependencies

  • software.visionary:cache:2026.10.13 at Compile
  • software.visionary.contrax:processor:2026.10.13 at Processor
  • software.visionary:junit-closure:2026.09.17 at Testing

Views

Drawn from the release. A link in a view opens its source at the release tag.

architecture.svg
architecture.svg
classes.software.visionary.cache.eviction.frequency.svg
classes.software.visionary.cache.eviction.frequency.svg
packages.svg
packages.svg