TL;DR

  • 场景: 线上项目广泛使用 Guava Cache,但对 LocalCache 具体行为缺乏认知
  • 结论: Guava 通过 LocalCache+Segment 分段结构、引用队列和访问/写入队列,实现并发安全的本地缓存
  • 产出: 核心结构解析 + 回收机制说明 + 常见问题排查

Guava Cache 与 ConcurrentHashMap 的根本差异

特性ConcurrentHashMapGuava Cache
生命周期永久存储,需显式remove支持自动过期回收
回收机制容量/时间/引用回收
适用场景长期配置信息高频访问的临时数据

LocalCache 核心结构

1. 分段锁设计(Segment-based Locking)

  • LocalCache 内部维护 Segment 数组
  • 通过 concurrencyLevel 参数控制分段数量(默认4)
  • 读操作无锁(volatile),写操作需获取 Segment 锁

2. 五大队列机制

  • keyReferenceQueue: 跟踪被GC回收的弱引用键
  • valueReferenceQueue: 跟踪被GC回收的弱/软引用值
  • recencyQueue: 临时记录最近访问的条目
  • writeQueue: 按写入时间排序(LRU)
  • accessQueue: 按访问时间排序

3. 数据表结构

AtomicReferenceArray<ReferenceEntry<K,V>> table: 哈希表存储

ReferenceEntry 包含:key、hash、valueReference、next 字段

回收机制

1. 基于容量回收

使用 LRU(最近最少使用)策略淘汰

2. 定时回收

  • expireAfterAccess: 访问后过期
  • expireAfterWrite: 写入后过期

3. 基于引用回收

支持弱引用键、弱引用值、软引用值

惰性清理机制(Lazy Eviction)

Guava Cache 采用惰性清理,不会主动扫描整个缓存:

  1. get() 操作时: 检查键对应条目是否过期,过期则移除
  2. put() 操作时: 检查缓存大小是否超限,超限则触发LRU回收

常见问题排查

症状根因修复方案
内存持续增长未设置 maximumSize配置 maximumSize/maximumWeight
命中率低过期时间过短调整 expireAfterAccess/Write
无流量后内存暴涨惰性清理未触发定期调用 cache.cleanUp()
弱引用导致数据丢失GC 回收过于频繁仅在内存压力时使用弱引用