• JS垃圾回收与内存泄漏


    垃圾回收与内存泄漏

    内存泄露

    程序的运行需要内存。只要程序提出要求,操作系统或者运行时(runtime)就必须供给内存。
    对于持续运行的服务进程(daemon),必须及时释放不再用到的内存。否则,内存占用越来越高,轻则影响系统性能,重则导致进程崩溃。
    也就是说,不再用到的内存,如果没有及时释放,就叫做内存泄漏(memory leak)。

    JavaScript 中的垃圾回收

    浏览器的 Javascript 具有自动垃圾回收机制GCGarbage Collecation),也就是说,执行环境会负责管理代码执行过程中使用的内存。其原理是:垃圾收集器会定期(周期性)找出那些不在继续使用的变量,然后释放其内存

    但是这个过程不是实时的,因为其开销比较大并且 GC 时停止响应其他操作,所以垃圾回收器会按照固定的时间间隔周期性的执行。

    不再使用的变量也就是生命周期结束的变量,当然只可能是局部变量,全局变量的生命周期直至浏览器卸载页面才会结束。局部变量只在函数的执行过程中存在,而在这个过程中会为局部变量在栈或堆上分配相应的空间,以存储它们的值,然后在函数中使用这些变量,直至函数结束,而闭包中由于内部函数的原因,外部函数并不能算是结束。

    下面是一段示例代码:

    function fn1() {
        var obj = {name: 'xiaocan', age: 23};
    }
    function fn2() {
        var obj = {name:'xiaocantongxue', age: 23};
        return obj;
    }
    
    var a = fn1();
    var b = fn2();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    在上面的代码中,我们首先声明了两个函数,分别叫做 fn1fn2

    fn1 被调用时,进入 fn1 的环境,会开辟一块内存存放对象 {name: ‘xiaocan’, age: 10}。而当调用结束后,出了 fn1 的环境,那么该块内存会被 JavaScript 引擎中的垃圾回收器自动释放;

    fn2 被调用的过程中,返回的对象被全局变量 b 所指向,所以该块内存并不会被释放。

    这里问题就出现了:到底哪个变量是没有用的?

    所以垃圾收集器必须跟踪到底哪个变量没用,对于不再有用的变量打上标记,以备将来收回其内存。

    用于标记的无用变量的策略可能因实现而有所区别,通常情况下有两种实现方式:标记清除引用计数

    引用计数不太常用,标记清除较为常用。

    标记清除

    JavaScript 中最常用的垃圾回收方式就是标记清除。

    当变量进入环境时,例如,在函数中声明一个变量,就将这个变量标记为“进入环境”。

    从逻辑上讲,永远不能释放进入环境的变量所占用的内存,因为只要执行流进入相应的环境,就可能会用到它们。

    而当变量离开环境时,则将其标记为“离开环境”。

    function test(){
      var a = 10 ; // 被标记 ,进入环境 
      var b = 20 ; // 被标记 ,进入环境
    }
    test(); // 执行完毕 之后 a、b 又被标离开环境,被回收。
    
    • 1
    • 2
    • 3
    • 4
    • 5

    垃圾回收器在运行的时候会给存储在内存中的所有变量都加上标记(当然,可以使用任何标记方式)。

    然后,它会去掉环境中的变量以及被环境中的变量引用的变量的标记(闭包)。而在此之后再被加上标记的变量将被视为准备删除的变量,原因是环境中的变量已经无法访问到这些变量了。

    最后,垃圾回收器完成内存清除工作,销毁那些带标记的值并回收它们所占用的内存空间。

    到目前为止,IE9+、Firefox、Opera、Chrome、SafariJS 实现使用的都是标记清除的垃圾回收策略或类似的策略,只不过垃圾收集的时间间隔互不相同。

    引用计数

    引用计数的含义是跟踪记录每个值被引用的次数。

    当声明了一个变量并将一个引用类型值赋给该变量时,则这个值的引用次数就是 1。如果同一个值又被赋给另一个变量,则该值的引用次数加 1

    相反,如果包含对这个值引用的变量又取得了另外一个值,则这个值的引用次数减 1。当这个值的引用次数变成 0 时,则说明没有办法再访问这个值了,因而就可以将其占用的内存空间回收回来。

    这样,当垃圾回收器下次再运行时,它就会释放那些引用次数为 0 的值所占用的内存。

    function test() {
        var a = {};	// a 指向对象的引用次数为 1
        var b = a;	// a 指向对象的引用次数加 1,为 2
        var c = a;	// a 指向对象的引用次数再加 1,为 3
        var b = {};	// a 指向对象的引用次数减 1,为 2
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    Netscape Navigator3 是最早使用引用计数策略的浏览器,但很快它就遇到一个严重的问题:循环引用

    循环引用指的是对象 A 中包含一个指向对象B的指针,而对象 B 中也包含一个指向对象 A 的引用。

    function fn() {
        var a = {};
        var b = {};
        a.pro = b;
        b.pro = a;
    }
    fn();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    以上代码 ab 的引用次数都是 2fn 执行完毕后,两个对象都已经离开环境,在标记清除方式下是没有问题的,但是在引用计数策略下,因为 ab 的引用次数不为 0,所以不会被垃圾回收器回收内存,如果 fn 函数被大量调用,就会造成内存泄露。在 IE7IE8 上,内存直线上升。

    JavaScript中的垃圾回收站机制

    JavaScript 具有自动垃圾回收机制。垃圾收集器会按照固定的时间间隔周期性的执行。

    JavaScript 常见的垃圾回收方式:标记清除引用计数方式。

    1、标记清除方式:

    • 工作原理:当变量进入环境时,将这个变量标记为“进入环境”。当变量离开环境时,则将其标记为“离开环境”。标记“离开环境”的就回收内存。

    • 工作流程:

    • 垃圾回收器,在运行的时候会给存储在内存中的所有变量都加上标记;

    • 去掉环境中的变量以及被环境中的变量引用的变量的标记;

    • 被加上标记的会被视为准备删除的变量;

    • 垃圾回收器完成内存清理工作,销毁那些带标记的值并回收他们所占用的内存空间。

    2、引用计数方式:

    • 工作原理:跟踪记录每个值被引用的次数。

    • 工作流程:

    • 声明了一个变量并将一个引用类型的值赋值给这个变量,这个引用类型值的引用次数就是 1

    • 同一个值又被赋值给另一个变量,这个引用类型值的引用次数加 1

    • 当包含这个引用类型值的变量又被赋值成另一个值了,那么这个引用类型值的引用次数减 1

    • 当引用次数变成 0 时,说明没办法访问这个值了;

    • 当垃圾收集器下一次运行时,它就会释放引用次数是 0 的值所占的内存。


    以上笔记整理于渡一教育谢老师课堂

  • 相关阅读:
    openvino 将onnx转为IR并进行int8量化
    c++ 原子变量-Memory fence
    Dropdown 下拉菜单实现标签页的相关操作
    react库的基础学习
    YOLOv6:又快又准的目标检测框架开源啦
    3 Thymeleaf 常用语法
    基于SVM+TensorFlow+Django的酒店评论打分智能推荐系统——机器学习算法应用(含python工程源码)+数据集+模型(二)
    Spring Boot整合Lombok全过程及详细用法
    “论软件系统架构评估”精选范文,软考高级论文,系统架构设计师论文
    性能分析之解析 RESAR 性能分析七步法
  • 原文地址:https://blog.csdn.net/weixin_45463061/article/details/126611957