WeakHashMap + Integer 导致数据丢失(RN 埋点案例)

WeakHashMap 用自动装箱的 Integer 作 Key 时,超出 Integer 缓存区间的值往往只有弱引用指向,GC 后键被回收,映射随之丢失。


场景与现象


根因:WeakHashMap × Integer 装箱

  1. WeakHashMap 的键是弱引用
    Key 在表中仅以弱引用关联;若 Key 对象没有其它强引用,GC 可回收该 Key,对应条目失效。

  2. Integer.valueOf 与缓存
    对默认区间(通常为 -128127)返回缓存单例,缓存由 JVM 长期强引用。超出区间则 new Integer(i);若调用栈上再无其它引用,该 Key 可能仅被 WeakHashMap 的弱引用指向

  3. 叠加后的表现
    较小的 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 的影响
落在默认缓存内(常见为 -128127返回缓存实例,存在强引用链Key 不易因「仅有弱引用」而消失,映射相对稳定
超出缓存(例如 ≥ 131;步长 10 时约对应第 14 个页面量级)可能每次装箱为新对象若无额外强引用持有该 Integer,GC 后弱引用键被清理,易出现丢数据

说明:实际「第几页开始出问题」取决于 rootTag 分配规则、步长、是否复用装箱对象等。复盘时以是否仍依赖 Integer 缓存带来的强引用为分界线,不必死记某一页码。


解决方案

强引用表 + 业务侧清理

若业务只有数值 ID、拿不到 View,则不要WeakHashMap<Integer, …> 依赖装箱与 GC 的偶然性。应改用 HashMap(或 SparseArray 等),在页面销毁或引擎回调里 remove / clear,用明确的生命周期管理容量与一致性。

HashMap<Integer, BundleInfo> cache = new HashMap<>();