美文网首页
project中的堆栈内存,内存地址引用,gc相关问题

project中的堆栈内存,内存地址引用,gc相关问题

作者: 田帅奇 | 来源:发表于2018-10-31 12:13 被阅读0次

    项目中遇到了关于js内存,引用类型,gc机制相关的问题,记录下来。
    首先复现一下代码:

    const option = {
      yAxis: [{
        type: 'value',
        ......  
      }]
    };
    option.yAxis.push(option.yAxis[0]);
    option.yAxis[0].min = 1;
    option.yAxis[1].min = 2;
    

    发现option.yAxis下面的min的值都是2;第一反应是以为之前的代码写的有问题,所以打断点,打日志,发现都正常,然后想到了有可能是内存指针导致的,然后可以做以下两种修改。

    const option = {
      yAxis: [{
        type: 'value',
        ......  
      }]
    };
    option.yAxis.push(JSON.parse(JSON.stringify(option.yAxis[0])));
    option.yAxis[0].min = 1;
    option.yAxis[1].min = 2;
    

    或者

    const option = {
      yAxis: [{
        type: 'value',
        ......  
      }]
    };
    option.yAxis.push(option.yAxis[0]);
    option.yAxis[0] = {min: 1};
    option.yAxis[1] = {min: 2};
    

    以上两种写法都可以规避内存指针引用导致的数据问题。

    剖析以下问题的本质吧:

    1. JS基本数据类型:Null,Undefined,Number,Boolean,Symbol,String
    2. JS复杂数据类型:Object Array (也算是object)

    当我们在代码里面:

    var num = 123;
    var obj = { name: 'obj' };
    var obj_copy = obj;
    

    此时会开辟两块内存空间,一个存储123,一个存储 { name: 'obj' }; 其中是num的指针会直接指向栈内存中的123,obj的指针会指向堆内存中的 { name: 'obj' },obj_copy的引用指针会存放在栈内存中;
    如果我们编辑以下代码:

    var num = 123;
    var obj = { name: 'obj' };
    var obj_copy = obj;
    obj.name = 'xxx';
    

    那么我们会发现obj_copy的name也会变成xxx,其实就是因为产生了指针以引用,指向的是同一块内存空间;
    如果编辑以下代码:

    var num = 123;
    var obj = { name: 'obj' };
    var obj_copy = obj;
    obj = {name: 'xxx'};
    

    那么我们会发现obj_copy的name还是obj,这是因为obj = {name: 'xxx'};会产生一块新的内存空间,然后obj会产生一次引用,obj_copy的引用跟该引用没有任何毛线关系。那么我们项目中的问题就迎刃而解了。

    写到这里,突然想到了es6 的const 变量申明:

    const  object = { name: 'object'};
    const num = 5;
    object.name = 'new_object';
    num = 4;
    

    我们会发现object的name确实变成了字符串new_object,但是num=4会报错,为什么呢?其实这个问题跟堆栈内存没有关系,还是跟引用数据类型有关;
    看下面的代码:

    let numA = 1;
    let numB = numA;
    numA = 2;
    console.log(numB );
    

    我们发现numB的值仍然是1,其实基本数据类型也是存在引用的,只是基本数据类型无法像object一样去更改某个key的值而已,就比如一座房子和一把板凳,如果你改变了房子或者凳子,那么他就是实实在在地改变了,如果你只是改变了一座房子内部的一部分,它仍然是房子。

    说到这里,不得不提一下es6的 WeekMap了(也是借用了阮大的例子吧,哈哈哈!)

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

    WeekMap 是一种弱引用,也就是说,该节点的引用计数是1,如果element设置为null,Weakmap 保存的这个键值对,也会自动消失。

    说到这里,就要说一下javascript的垃圾回收机制了,分为两种类型吧,一种是标记清除 ,一种是引用计数。

    let fn = function (){
      let a = 1;
      return a;
    }
    fn();
    

    在执行fn函数时,变量a被标记为进入环境,在函数没有被执行结束之前,是不能释放该变量所指向的内存的,当函数执行完之后,变量会被标记为离开环境,则会被gc回收

    let fn = function (){
      let a = 1;
      return a;
    }
    let back = fn();
    

    如果代码写成这样的话,变量a所占用的内存是不会被gc的,因为在外部存在了引用,虽然a已经离开了fn的执行环境,但是a的引用计数是2,所以不会被gc回收清除。

    function problem(){     
        var objectA = new Object();
        var objectB = new Object(); 
    
        objectA.someOtherObject = objectB;
        objectB.anotherObject = objectA; 
    } 
    

    上述情况也是objectA 和B都离开了函数环境,但是因为存在循环引用,所以引用计数都不为0,所以内存就不会得到回收,在个别情况下,需要手动回收。
    另外,gc时,会阻塞主线程,所以平常写代码的时候一定要注意相关问题,务必规避内存泄漏的相关问题。

    相关文章

      网友评论

          本文标题:project中的堆栈内存,内存地址引用,gc相关问题

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