• 内存机制及内存泄漏相关总结


    内存空间

    常用数据结构:

    1. 栈数据结构:后进先出(LIFO)
    2. 堆数据结构
    3. 队列:先进先出(FIFO),事件循环的基础结构

    JS内存空间:

    1. 栈(stack):存放变量
    2. 堆(heap):存放复杂对象
    3. 池:一般归为栈,存放常量

    注意闭包中的变量不存放在栈中,而是存放在堆中!!

    变量的存放:

    1. 基本数据类型:保存在栈中
    2. 引用数据类型:对象保存在堆内存中,因为JS不允许直接访问堆内存中的位置,因此在操作对象时实际是在操作对象的引用(即保存在栈中的一个地址)而不是实际的对象

    QUESTION:为什么会有栈内存和堆内存的区别?

    由于垃圾回收机制,为了使程序运行所占用的空间最小。

    var a = {n: 1};
    var b = a;
    a.x = a = {n: 2};
    
    a.x     // 这时 a.x 的值是多少
    b.x     // 这时 b.x 的值是多少

    上面这个问题的结果:a.x值为undefined,b.x的值为{n:2}

    重点在 a.x = a = {n:2};这句!

    赋值运算是从左到右解析:

    a.x = a  [ = undefined  ]    得到两个引用,a.x表示在{n:1}这个对象中新增加了一个x

    a = {n:2}

    从右到左赋值:

    a = {n:2}   这句的意思是给a重新赋值,将a的引用指向新的对象,即指向{n:2}

    a.x = ( a = {n:2} )  此时将{n:2}赋值给x,此时,由于b指向的地址没有变,则b的当前对象为{ n:1, x: {n:2} }

    so

    a.x值为undefined,因为新的a里没有对象x

    b.x值为{n:2}

    内存回收

    局部变量和全局变量的销毁:

    • 在函数执行完后,局部变量就没存在的必要了,垃圾回收器可以很容易的做出判断并回收
    • 全局变量什么时候自动释放内存空间很难判断,所以要尽量避免使用全局变量。如果必须使用全局变量存储大数据时,确保使用完之后将它设置为null或重新定义

    垃圾回收算法的核心:如何判断内存已经不再使用了。

    垃圾回收算法:引用计数(目前只有老IE在用,);标记清除【Mark-and-sweep】(现代浏览器,常用)

    引用计数算法定义“内存不再使用”的标准是:看一个对象是否有指向它的引用,如果没有其他对象指向它,即引用数为0,就说明该对象已经不再需要了,可以释放该内存。

    如果一个值不需要了,但是引用数却不为0,那么垃圾回收机制就没法释放这块内存,从而导致内存泄露。

    let arr = [1, 2, 3, 4];
    console.log('hello world');

    上面这个代码中,[1,2,3,4]是一个值,会占用内存,变量arr是对这个值的引用,so引用数为1,尽管后面的代码没有用到arr,它还是会持续占用内存。

    如果在最后一行增加一句:arr = null; 则解除了arr对[1,2,3,4]的引用,这块内存就可以被释放了。

    // 创建一个对象person,他有两个指向属性age和name的引用
    var person = {
        age: 12,
        name: 'aaaa'
    };
    
    person.name = null; // 虽然name设置为null,但因为person对象还有指向name的引用,因此name不会回收
    
    var p = person; 
    person = 1;         //原来的person对象被赋值为1,但因为有新引用p指向原person对象,因此它不会被回收
    
    p = null;           //原person对象已经没有引用,很快会被回收

    上面的例子很好的解释了什么是“没有其他对象指向它

    引用计数还有一个问题是:循环引用!如果两个对象相互引用,尽管它们不再使用了,但是还是不会回收,从而造成内存泄露。

     标记清除算法将“不再使用的对象”定义为“无法到达的对象”。从根部(全局对象)开始,凡是能从根部到达的对象,都是还需要使用的!无法由根部出发触及到的对象(什么意思?!)(包含没有任何引用的对象)被标记为不再使用,稍后进行回收。

    现代的垃圾回收器改良算法,本质为:可达内存被标记,其余的被当做垃圾回收

    var div = document.createElement("div");
    div.onclick = function() {
        console.log("click");
    };

    这个例子我没搞懂为啥在标记清除算法下就可以回收,在引用计数算法下不能回收?!

    QUESTION:从内存来看null和undefined本质区别是什么??

    null就是一切虚无,什么都没有

    undefined是里面有东西,但是未知

    WeakMap和WeakSet

    ES6提出的新的数据结构,它们对于值的引用都是不计入垃圾回收机制的。(???啥意思???)

    const wm = new WeakMap();
    const element = document.getElementById('example');
    
    wm.set(element, 'some information');
    wm.get(element) // "some information"

    以上代码新建一个WeakMap实例,将一个DOM节点作为键名存入该实例,‘some information’作为键值一起存放在WeakMap里。

    这时,WeakMap对element的引用就是弱引用,即DOM节点对象的引用计数是1,这时,一旦消除对该节点的引用,它占用的内存就会被释放,WeakMap保存的这个键值对也会自动消失。

     

    常见的JavaScript内存泄露:

    1. 意外的全局变量:比如在函数内部变量忘记用var声明,实际上JS会把这个变量挂载到全局对象上,这样就意外创建了一个全局变量!
      • 解决办法:使用'use strict'严格模式
    2. 被遗忘的计时器或回调函数
      • 注意点:定时器要及时remove 
      • addEventListener这类观察者的例子,目前现代浏览器能很好的检测和处理循环引用,因此在处理回收节点内存的问题时,不需要非要调用removeEventListener
    3. 脱离DOM的引用
      • 如果把多个DOM存到一个对象里,即使删除了这些DOM节点,但是对象里还是有对DOM的引用,因此无法回收
      • 解决:在删除DOM前,把关于这个DOM的引用(比如绑定事件)置为null
    4. 闭包

     

  • 相关阅读:
    python学习之控制语句
    linux中的网络基础
    python学习之准备
    linux用户权限
    python学习之函数和函数参数
    python学习之输出与文件读写
    linux中的vim编辑器的使用
    从产品和用户角度,思考需求和用户体验
    好记性不如烂笔头
    TI DaVinci(达芬奇)入门
  • 原文地址:https://www.cnblogs.com/ningyn0712/p/11651989.html
Copyright © 2020-2023  润新知