TL;DR
- 场景: 线上项目广泛使用 Guava Cache,但对 LocalCache 具体行为缺乏认知
- 结论: Guava 通过 LocalCache+Segment 分段结构、引用队列和访问/写入队列,实现并发安全的本地缓存
- 产出: 核心结构解析 + 回收机制说明 + 常见问题排查
Guava Cache 与 ConcurrentHashMap 的根本差异
| 特性 | ConcurrentHashMap | Guava 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 采用惰性清理,不会主动扫描整个缓存:
- get() 操作时: 检查键对应条目是否过期,过期则移除
- put() 操作时: 检查缓存大小是否超限,超限则触发LRU回收
常见问题排查
| 症状 | 根因 | 修复方案 |
|---|---|---|
| 内存持续增长 | 未设置 maximumSize | 配置 maximumSize/maximumWeight |
| 命中率低 | 过期时间过短 | 调整 expireAfterAccess/Write |
| 无流量后内存暴涨 | 惰性清理未触发 | 定期调用 cache.cleanUp() |
| 弱引用导致数据丢失 | GC 回收过于频繁 | 仅在内存压力时使用弱引用 |