news 2026/7/1 1:17:05

内存管理 - 内存泄漏 - 排查、预防策略

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
内存管理 - 内存泄漏 - 排查、预防策略

浏览器内存模型 - 由JS引擎管理

在浏览器环境下就是 V8引擎

栈内存:存放原始类型值,生命周期短,不由GC回收而是由引擎自动弹栈

堆内存:存放引用数据类型,由GC进行回收

垃圾回收机制

JS 是自动垃圾回收语言,不用显式地 free()

JS 的 GC 主要依赖可达性,当一个对象从根对象不可达时就会判定为垃圾

根对象包括:

  • 全局对象 window / globalThis
  • 当前执行栈
  • 闭包引用的变量
  • DOM树

现代 V8 引擎主要用两种 GC 算法

  1. 标记清除:从根对象开始扫描,对所有可达对象打上标签,如果没有标记的就进行清除

  2. 分代垃圾回收:V8会把堆分为两部分

    1. 新生代:对象存活时间短、GC频繁、速度快,例如局部变量
    2. 老生代:存活时间久的,如大型对象、闭包、全局缓存

    新生代用复制算法(将内存分为From空间和To空间,每次垃圾回收时把From里仍存活的对象复制到To中,再交换两个空间角色,那么没有复制的自然久变成了垃圾)

    老生代用标记压缩算法(标记、清除、如果有必要再压缩,如果分配一个大对象时由于内存没有连续的空间会导致GC频繁触发,所以就必须压缩整理碎片)

前端常见的内存泄漏

本质是 GC Root(window / 定时器 / 事件系统 / 缓存)持有了生命周期本应结束的对象引用

一. 长期存活对象持有了不该持有的引用

GC Root → 长生命周期对象 → 业务对象

1. 定时器 / 异步任务未清理

setInterval(() => { doSomething(this.bigData) }, 1000)

定时器由浏览器调度系统长期持有,我们传入的回调函数中闭包隐式引用了外部对象

那么就会导致如果一直没有 clearInterval 的话外部对象就一直无法 GC

类似于 安卓开发中 Hanlder + postDelay 导致的泄漏

2. 事件监听器未移除

window.addEventListener('scroll', handler)

像 window / document 是天然的 GC Root

handler 闭包引用组件的实例,即使组件卸载监听仍然存在

正确的清理方式是通过在 useEffect 中返回卸载函数

useEffect(() => { window.addEventListener('scroll', handler) return () => window.removeEventListener('scroll', handler) }, [])

3. 闭包捕获大对象

function bad() { const bigData = new Array(10000) return () => bigData }

返回函数持有 bigData

闭包生命周期 > 预期,导致数据无法释放

类似于 Java 中非静态内部类隐式引用了外部类的对象

4. 缓存结构使用不当

const map = new Map() map.set(obj, value)

Map 对 key 是强引用,如果缓存没有淘汰的话就永久存活,应该优先使用 weakMap

const wm = new WeakMap() wm.set(obj, value)

二. 全局作用域污染 - 隐式GC Root

5. 意外创建全局对象

function fn() { leak = 'oops' }

没有进行 var / const / let导致对象被挂到全局对象上,那么就跟着全局对象的生命周期( var 挂到的是当前作用域的顶层对象,只要不是在全局作用域中就不会挂到全局对象上)

本质就是被静态量引用导致在程序进程结束后才会清除

三. DOM 和 JS 引用生命周期不一致

6. DOM 已经移除但是 JS 仍然持有引用

const el = document.getElementById('box') document.body.removeChild(el) // el 仍然被 JS 引用

DOM 树已经断开但是JS强引用仍在,导致节点无法 GC

四. 外部资源泄漏

像 C++ 有析构函数能在一个对象清除的时候顺带将与其关联的外部资源倾销

7. Object URL

const url = URL.createObjectURL(file) img.src = url

浏览器内部维护一块 Blob → 内存/磁盘映射

url 是一个资源句柄式的引用,不手动 revoke 的话会导致内存不会释放

URL.revokeObjectURL(url)

8. 文件/IO数据流

虽然 Node.js 不是前端但是也顺带提一嘴

const fs = require('fs') fs.open('a.txt', 'r', (err, fd) => { // 忘记 fs.close(fd) })

9. 浏览器 File System Access API

const handle = await window.showOpenFilePicker() const fileHandle = handle[0] const writable = await fileHandle.createWritable() // ... // 忘记 writable.close()

进行排查

通过 开发者工具中的性能 查看 Memory profiling 和堆快照,找一直没有清除的甚至一直增长的内存空间

设置过期策略

1. TTL:时间过期

看 Date.now() 是否大于 cached.expire 设置的过期时间,如果超过了就 delete

一般包装在 localStorage 中,因为 localStorage 没有自动过期机制

const cache = new Map(); function setCache(key, value, ttl = 5000) { cache.set(key, { value, expire: Date.now() + ttl, }); } function getCache(key) { const cached = cache.get(key); if (!cached) return null; if (Date.now() > cached.expire) { cache.delete(key); return null; } return cached.value; }

2. LRU 缓存:数量有限、最近使用的

每次 set 和 get 的时候就更新优先级(JS 的 Map 是自动有序的)

class LRUCache { constructor(limit = 100) { this.limit = limit; this.map = new Map(); } get(key) { if (!this.map.has(key)) return null; const value = this.map.get(key); // 每次访问移动到结尾(最新使用) this.map.delete(key); this.map.set(key, value); return value; } set(key, value) { if (this.map.has(key)) this.map.delete(key); else if (this.map.size >= this.limit) { // 删除最久未使用的值(Map 的第一个值) const oldestKey = this.map.keys().next().value; this.map.delete(oldestKey); } this.map.set(key, value); } }

3. LFU:根据访问次数进行淘汰

4. 内存占用的清除

管理一个队列

let currentSize = 0; const MAX_SIZE = 5 * 1024 * 1024; // 5MB const cache = []; function addToCache(obj) { const size = JSON.stringify(obj).length; while (currentSize + size > MAX_SIZE) { const removed = cache.shift(); // 移除一个最旧的 currentSize -= JSON.stringify(removed).length; } cache.push(obj); currentSize += size; }

5. 利用 WeakMap 不是强引用,让GC自动清除

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/30 7:58:24

【高产农业模型构建秘诀】:不可忽视的R语言回归诊断7大指标

第一章:农业产量回归模型的构建背景与挑战在现代农业科学中,准确预测作物产量对于粮食安全、资源分配和政策制定具有重要意义。随着传感器技术、遥感数据和气象监测系统的普及,农业生产过程中积累了大量多源异构数据。利用这些数据构建精准的…

作者头像 李华
网站建设 2026/6/30 23:00:07

智慧旅游交通专题汇总(2025-12-12更新)

智慧旅游是一种将现代信息技术与旅游业相结合的新型旅游方式。它通过智能化手段,为游客提供更加便捷、个性化的服务,同时提升旅游行业的运营效率和管理水平。智慧旅游包括导航、导游、旅游管理、营销等多个方面的智能化应用,旨在为游客带来更…

作者头像 李华
网站建设 2026/6/30 21:48:39

机器学习开展因果推断研究,必定是未来医学科研的大趋势

源自风暴统计网:一键统计分析与绘图的网站因果推断与机器学习在近年来相互影响和促进,在实践中的应用越来越多。在医学科研领域,虽然通过机器学习方法开展因果推断研究,越来越受重视,但目前来看应用不足。多数研究使用…

作者头像 李华
网站建设 2026/6/28 18:28:42

终极键盘布局编辑器:轻松设计个性化键盘的免费在线工具

终极键盘布局编辑器:轻松设计个性化键盘的免费在线工具 【免费下载链接】keyboard-layout-editor Web application to enable the design & editing of keyboard layouts 项目地址: https://gitcode.com/gh_mirrors/ke/keyboard-layout-editor 想要打造专…

作者头像 李华
网站建设 2026/6/30 11:56:38

在 ABAP 里实现 CGLIB 思想:用动态代理做非侵入式增强、测试替身与方法 Exit

在不少 Java 体系里,动态代理 是一把非常好用的“手术刀”:你不去碰原来的业务代码,却能在方法调用的入口和出口塞进日志、鉴权、性能埋点、灰度开关、缓存等横切逻辑。对长期和 SAP 打交道的 ABAP 开发者来说,这种感觉并不陌生——我们早就习惯了 enhancement、BAdI、隐式…

作者头像 李华