WeakHashMap + Integer 导致数据丢失(RN 埋点案例)
WeakHashMap用自动装箱的Integer作 Key 时,超出Integer缓存区间的值往往只有弱引用指向,GC 后键被回收,映射随之丢失。
场景与现象
- 用法:
WeakHashMap<Integer, BundleInfo>存放 RN 页面元数据。 - Key:RN
rootTag(Integer),从 1 递增,步长 10(1, 11, 21, …)。 - 现象:应用初期前几页埋点正常;页面增多后,埋点数据经常变空。根因是 Map 中条目被 GC 清理,而非业务逻辑未写入。
根因:WeakHashMap × Integer 装箱
WeakHashMap的键是弱引用
Key 在表中仅以弱引用关联;若 Key 对象没有其它强引用,GC 可回收该 Key,对应条目失效。Integer.valueOf与缓存
对默认区间(通常为-128~127)返回缓存单例,缓存由 JVM 长期强引用。超出区间则new Integer(i);若调用栈上再无其它引用,该 Key 可能仅被WeakHashMap的弱引用指向。叠加后的表现
较小的rootTag命中缓存时,Key 与常量池绑定,不易被单独回收;超过缓存上界后,每次装箱可能得到「仅被弱引用持有」的Integer,GC 后表现为数据突然消失。
关键阈值(与 RN rootTag 步长)
JDK 中 Integer.valueOf 的核心逻辑可概括为:
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high) // 默认 high 常为 127
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
| rootTag 大致范围 | 装箱行为 | 对 WeakHashMap 的影响 |
|---|---|---|
落在默认缓存内(常见为 -128~127) | 返回缓存实例,存在强引用链 | Key 不易因「仅有弱引用」而消失,映射相对稳定 |
| 超出缓存(例如 ≥ 131;步长 10 时约对应第 14 个页面量级) | 可能每次装箱为新对象 | 若无额外强引用持有该 Integer,GC 后弱引用键被清理,易出现丢数据 |
说明:实际「第几页开始出问题」取决于 rootTag 分配规则、步长、是否复用装箱对象等。复盘时以是否仍依赖 Integer 缓存带来的强引用为分界线,不必死记某一页码。
解决方案
强引用表 + 业务侧清理
若业务只有数值 ID、拿不到 View,则不要用 WeakHashMap<Integer, …> 依赖装箱与 GC 的偶然性。应改用 HashMap(或 SparseArray 等),在页面销毁或引擎回调里 remove / clear,用明确的生命周期管理容量与一致性。
HashMap<Integer, BundleInfo> cache = new HashMap<>();