JS垃圾回收机制与内存泄漏
2022-07-08 本文已影响0人
前端末晨曦吖
内存泄漏
浏览器封装的V8引擎支持对JS进行解析,当程序运行(runtime)时,只要程序提出需要内存,例如声明赋值字符串、对象、数组等变量时;操作系统就必须给分配内存使用,对于持续运行的服务进程,变量不断增加,并且内存没有得到及时的释放,内存占有会越来越大,轻则影响系统性能,重则直接导致系统崩溃。 不再用到的内存,没有及时释放,就叫做内存泄漏。
有些语言(比如c语言)必须手动释放内存,程序员负责内存管理。
这很麻烦,所以大多数语言提供自动内存管理,减轻程序员的负担,这被称为垃圾回收机制。
内存泄漏的识别方法
浏览器
使用快捷键 F12 或者 Ctrl+Shift+J 打开 Chrome 浏览器的「开发者工具」。
选择 Performance(老版为Timeline) 选项卡,在 Capture 选项中,只勾选 Memory。
设置完成后,点击最左边的 Record 按钮,然后就可以访问网页了。
打开一个网站,例如:www.taobao.com,当网页加载完成后,点击 Stop,等待分析结果。
然后在 ChartView 上寻找内存急速下降的部分,查看对应的 EventLog,可以从中找到 GC 的日志。
如果内存占用基本平稳,接近水平,就说明不存在内存泄漏。 反之,就是内存泄漏了。
命令行
命令行可以使用 Node 提供的process.memoryUsage方法。
内存泄露的常见原因
意外的全局变量
dom清空时,还存在引用
定时器中的内存泄漏
不规范地使用闭包
避免策略
减少不必要的全局变量,或者生命周期较长的对象,及时对无用的数据进行垃圾回收(即赋值为null);
注意程序逻辑,避免“死循环”之类的 ;
避免创建过多的对象 原则:不用了的东西要记得及时归还。
减少层级过多的引用
垃圾回收机制
解决内存的泄露,垃圾回收机制会定期(周期性)找出那些不再用到的内存(变量),然后释放其内存。
标记清除(mark-and-sweep)
目前主流IE,Firefox,Opera,Chrome和Safari等浏览器均使用标记清除的垃圾收集策略。
js中最常用的垃圾回收方式就是标记清除。当变量进入环境时,例如,在一个函数中声明一个变量,就将这个变量标记为"进入环境",从逻辑上讲,永远不能释放进入环境变量所占用的内存,因为只要执行流进入相应的环境,就可能会用到它们。而当变量离开环境时,则将其标记为"离开环境"。
function test(){
var a = 10; //被标记"进入环境"
var b = "hello"; //被标记"进入环境"
}
test(); //执行完毕后之后,a和b又被标记"离开环境",被回收
工作流程
垃圾回收器,在运行的时候会给存储在内存中的所有变量都加上标记。
去掉全局中的变量以及被环境中的变量引用的变量的标记。
被加上标记的会被视为准备删除的变量。
垃圾回收器完成内存清除工作,销毁那些带标记的值并回收他们所占用的内存空间。
引用计数(reference counting)
语言引擎有一张"引用表",保存了内存里面所有资源(通常是各种值)的引用次数。如果一个值的引用次数是0,就表示这个值不再用到了,因此可以将这块内存释放。
引用计数的含义是跟踪记录每个值被引用的次数。当声明了一个变量并将一个引用类型值赋给该变量时,则这个值的引用次数就是1,如果同一个值又被赋给另一个变量,则该值的引用次数加1。相反,如果包含对这个值的引用的变量又取得了另外一个值,则这个值的引用次数减1,当这个值的引用次数为0时,则说明没有办法再访问这个值了,因此就可以将其占用的内存空间回收回来。
let aa = [] // 数组[]引用1次
let bb = aa // 数组引用2次
bb = null // 释放内存,引用还剩下1次,即变量aa的引用还存在
这样简单的垃圾回收机制,非常容易出现循环引用问题导致内存不能被回收, 进行导致内存泄露等问题; 如:
let aa = {}
let bb = {}
aa.c = bb
bb.c = aa
var wraper = document.querySelector('#btn');
wraper.onclick = handle;
wraper = null;
该方法并不能解除dom点击事件,因为释放的是wraper引用的这个变量,实际对象DOM已经绑定了这个btn点击事件,举个例子
let a = {}
let b = a
b.btn = () =>{}
b = null
console.log(a)