美文网首页js 世界
JS垃圾回收机制与内存泄漏

JS垃圾回收机制与内存泄漏

作者: 前端末晨曦吖 | 来源:发表于2022-07-08 20:41 被阅读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)
    

    相关文章

      网友评论

        本文标题:JS垃圾回收机制与内存泄漏

        本文链接:https://www.haomeiwen.com/subject/wpwvbrtx.html