全局缓存无限增长是内存泄漏的典型表现,本质是缓存对象长期被强引用、无淘汰机制、不随业务生命周期释放;需通过chrome任务管理器、performance面板和memory快照三步验证并定位强引用源头及高危代码模式。

全局缓存无限增长是内存泄漏的典型表现,本质是缓存对象长期被强引用、无淘汰机制、不随业务生命周期释放。
确认是否真在“无限增长”
别急着翻代码,先用浏览器任务管理器和 Performance 面板快速验证:
- 按 Shift + Esc 打开 Chrome 任务管理器,盯住目标标签页的“内存”列:静置 10 秒后操作一次(如刷新列表),再等 5 秒,重复 4–5 次——若内存每次上升且不回落,基本坐实增长趋势
- 打开 DevTools → Performance 面板,勾选 Memory,录制 20 秒含缓存写入的操作(如连续搜索、切换分页),停止后看 JS Heap 曲线:阶梯式抬升 + GC 后回落不足 10%,就是缓存正在“囤货”
- 在控制台执行
performance.memory.usedJSHeapSize,记下初始值;执行几次缓存写入后再次执行,若增长超 30% 且持续放大,说明缓存已失控
定位缓存对象本身
进 Memory 面板拍三张快照:
DMap GL地图JSAPI(dmapgl)开发指南。用于编写、审查、调试 DMap 地图 API 代码,涵盖地图初始化、覆盖物渲染、图层管理、事件处理、控件交互等任务。当用户提及 dmap、dmapgl、jsapi‑gl 或相关地图开发需求时自动触发。
- 刷新页面 → 拍 Snapshot 1(基线)
- 执行一次缓存写入(如调用
cache.set(key, hugeData))→ 拍 Snapshot 2 - 再执行两次相同操作 → 拍 Snapshot 3
选中 Snapshot 2 和 3,切到 Comparison 视图,重点筛选:
-
Constructor 为
Map、Object、Array或你自定义缓存类名(如LRUCache)的条目,看 # New 是否持续为正 - 点击异常增长的缓存实例,在右侧 Retainers 中查看引用路径——如果顶端是
window.xxxCache、module.exports.cache或某个长期存活的单例类实例,就锁定了强引用源头 - 展开其 Properties 或 Array 内容,确认是否存了大量重复 key、未序列化的 DOM 节点、或接口返回的原始大对象(如整页 JSON 数据)
查代码中的高危模式
缓存泄漏往往不是设计问题,而是使用失当。重点关注这几类写法:
-
模块顶层定义的 Map/Object:
const cache = new Map()写在文件最外层,所有导入它的模块共享同一份引用,且永不销毁 -
缓存 key 未标准化:比如用
JSON.stringify(obj)作 key,但每次生成新对象,导致 key 不同、旧值永远留着 -
没设容量上限或淘汰策略:只写
cache.set(key, value),从不调用cache.delete()或cache.clear(),也不用 LRU/LFU 等机制 -
缓存了不该缓存的东西:把
document.getElementById('chart')、this.state、或含闭包的函数直接塞进缓存,让整个作用域链被钉住 -
服务端渲染(SSR)残留:Node.js 环境中,
require缓存让模块级缓存跨请求复用,而前端又没做客户端清理,造成双重驻留
修复与加固建议
治标更要治本,从机制上切断无限增长路径:
- 把全局缓存改为**按需创建 + 显式销毁**:例如组件内用
useMemo(() => new Map(), []),卸载时调用.clear();避免模块级单例 - 强制 key 标准化:对对象 key 使用稳定哈希(如
object-hash库),对 URL 类 key 统一去参、转小写 - 引入带淘汰的缓存结构:用
lru-cache(Node)或quick-lru(浏览器),设置max和ttl,而非裸 Map - 敏感数据缓存加“生存期”标记:写入时记录时间戳,读取前检查是否过期;路由切换、用户登出时主动清空相关域缓存
- CI 阶段加入 ESLint 规则:如
no-restricted-syntax禁止顶层new Map(),配合no-global-assign防止挂 window










