垃圾回收 及 内存泄漏

一、JS 垃圾回收策略:

   垃圾回收策略有:标记清除 和 引用计数 。      (详细介绍请参考J《avaScript高级程序设计》)

  • 标记清除:

  • 引用计数:
    引用计数(Reference Counting),这其实是早先的一种垃圾回收算法,它把 对象是否不再需要 简化定义为 对象有没有其他对象引用到它,如果没有引用指向该对象(零引用),对象将被垃圾回收机制回收,目前很少使用这种算法了,因为它的问题很多,不过我们还是需要了解一下。
    【这种策略主要是在 循环引用时,会出现清除不了的现象

  • 总结:
    •  js中最常用的垃圾收集方式是标记清除。但是COM对象(BOM和DOM对象就是使用C++以COM对象的形式实现的)垃圾收集机制采用的就是引用计数策略。
       使用标记清除的方式,不会引起内存泄漏的问题(哪怕对象相互引用也没有关系)。因为变量离开执行环境就会被回收。
      (个人认为)现在的浏览器基本不用引用计数,只有早期的ie中com对象是使用引用计数的,现在应该都是使用标记清除,所以不要考虑内存泄漏的问题。
    •  使用引用计数策略, 在函数中出现循环引用的时候,当函数执行完毕,可是函数里的变量引用计数不为零,垃圾回收机制无法回收,这个对象将一直存在内存中。如果这个函数被多次调用的话,就会导致大量内存得不到回收。从而出现内存泄漏问题。
    •  解决循环引用的问题,在不使用变量的时候,手工断开原生JavaScript对象(获取DOM对象的变量)与DOM元素之间的链接。用element = null;
    •  JavaScript内存泄漏的质疑  : http://www.cnblogs.com/iyangyuan/p/4310601.html (个人觉得现代浏览器对垃圾回收已经做的很极致了。只是个别情况需要注意下手动释放下)

 

总结(个人观点):原生js对象不存在内存泄漏问题(原生js对象常用标记清除的垃圾回收策略),引起内存泄漏的问题主要还是在函数中对DOM的操作,同时DOM对象的属性指向函数的对象。内存泄漏不一定是闭包引起的,只是闭包函数不注意就会在闭包中出现对DOM的循环引用,重新内存泄漏的问题。

 参考: https://www.cnblogs.com/yhf286/p/4918095.html  或 https://www.cnblogs.com/sunhuahuaa/p/7655587.html (推荐)

具体  闭包函数的问题  有空看  JavaScript高级程序设计

 

二、js中给对象设置  null值  与  垃圾回收  的关系

  参考:https://www.cnblogs.com/cwWeb/archive/2012/07/14/2591956.html  或  https://zhidao.baidu.com/question/1175437169245726939.html

  1.   垃圾清理是针对对象的,不是针对某个标示符的。对于值类型,函数执行后,如果其所在作用域并未被应用,会立即释放;要销毁一个对象,必须 要消除一个对象 的所有外部 引用
      所以 将变量 设置 为 null,对象 不一定会被 垃圾回收。对象 只要 没有 外部 引用,自动回 被回收的。
  2.   非JS 创建的 对象(如DOM对象),js 的标识符(变量) 只是对它的引用。无法 通过设置 标识符 为 null ,将这样的对象 垃圾回收。因为 整个 web 环境还是有 引用他们的
      这样的对象有:DOM 树上DOM对象【js创建的DOM对象没有挂DOM树,就可以被垃圾回收】,BOM对象等。
      这种 web 环境 带有 的对象,要清除,还要看自身 是否有 这样的 API 去处理。

 

三、绑定事件 的对象,如果 对象被 回收了。那他所绑定的事件还 在吗,会被 回收吗。

  1.  首先 确认 对象是否真的被回收了,如果只是 指向的 标识符 设置 为了 null ,这个对象不一定 被回收了。  https://www.imooc.com/wenda/detail/490415
    var wraper = document.querySelector('#wraper');  //  获取 DOM 树上的DOM对象
    wraper.onclick = handle;
    wraper = null;  //  这里只是 把 wraper 标准 对 DOM 的引用断开了,但是 web 环境 对 这个DOM对象的 引用还是 存在的,所以这个DOM 对象并没有被回收。DOM对象的事件还是存在的。
  2.   确认 DOM 对象已经被 清除 回收了,那他所绑定的 事件函数 也会 被 垃圾回收掉。因为没有指向这个 事件函数的 东西了。
      不过 这点 可能 和  浏览器的 垃圾 回收 机制不同,好像低版本 的 IE 浏览器,需要手动设置 具名函数,函数名 为 null。
  3.   js中事件是 具名函数 和 匿名函数是有区别的:https://developer.aliyun.com/ask/66988?spm=a2c6h.13159736

 

四、内存泄露的4种方式及如何避免

   参考:https://www.pianshen.com/article/16561228160/ 或 https://blog.csdn.net/weixin_57092157/article/details/118596807

  【个人理解】正常的内存占用 属于 内存开销;该回收的内存无法回收,才叫内存泄漏。

      function foo(){
          bar = "我是全局变量"
      }
      //  正常情况 foo函数执行完毕,foo的执行栈内存都要释放出去。但是bar意外的变成 全局变量,bar 变量无法被回收。
  • 四种类型的常见 JavaScript 内存泄露

    • 意外的全局变量
      全局作用域下的变量,会在页面的生命周期内存续(即 一直存在)。《js高级程序设计第4版》

      function foo(arg){
          bar = "全局变量"  // 全局变量在生命周期中,都会存在,不会被回收的。需要手动设置 null 才会被回收
      }

      全局变量存储简单数据倒是无伤大雅的,不用考虑回收的问题。当全局变量用于临时存储和处理大量信息时,需要多加小心。如果必须使用全局变量存储大量数据时,确保用完以后把它设置为 null 或者重新定义。

    • 被遗忘的定时器或回调   【定时器本身不是什么问题,主要是回调函数,在函数中形成了闭包】

      function fn(){
          let name = 'jake'
          setInterval(() => {
              console.log(name);
          }, 100)
      }

      如果这个name是复制的引用类型数据,就比较占用内存了。如下

      var someResouce=getData();
      setInterval(function(){
          var node=document.getElementById('Node');
          if(node){
              node.innerHTML=JSON.stringify(someResouce)
          }
      },1000)

      这样的代码很常见,如果 id 为 Node 的元素从 DOM 中移除,该定时器仍会存在,同时,因为回调函数中包含对 someResource 的引用,定时器外面的 someResource 也不会被释放。

      【题外话】setTimeout 和 setInterval 调用时,会返回一个ID值(就是普通数字)。取消计时器,必须使用这个ID。这个ID只是基本数据类型,不占内存。

    • 脱离 DOM 的引用,【js引用的DOM对象,在DOM树中已经删除了,但是js保持对它的引用。js中这个DOM对象不会被回收掉。】

      var elements ={
          button: document.getElementById('button'),
          image: document.getElementById('image'),
          text: document.getElementById('text')
      }
      
      function doStuff() {
          image.src="http://some.url/image"
          button.click();
          console.log(text,innerHTML)
      }
      
      //更多逻辑
      function removeButton () {
          //按钮是body的后代元素
          document.body.remoyeChild(document.getElementById('button'))  // 这里虽然删除了DOM树上的这个DOM对象,但是js中还是保持者对它的引用
          //此时,仍日存在一个全局的button的引用
          //elements字典·button元素仍旧在内存中,不能被GC回收jdvd一日一条
      }
    • 闭包   【针对闭包函数, 如何避免内存泄露,请看 《闭包函数》文章】
      主要是函数里面给DOM对象添加事件函数,形成闭包。这时DOM对象一直引用着闭包函数,导致包含函数无法被回收。

      function assignHandler(){
          var element = document.getElementById('someElement')  // assignHandler作用域中,element变量引用了 document对象的属性
          element.onclick = () => console.log(element.id);  // 闭包中引用着 assignHandler() 的活动对象,阻止了对element的引用计数归零。
      }

       

  • 总结:JS的内存泄漏 都是因为 全局作用域 下,还是保持着 对 函数 活动对象的引用。导致函数执行完,而 活动对象无法被回收。如下说明
    • 被遗忘的定时器或回调 :这个还是本质上还是 因为形成闭包引起的。
    • 脱离 DOM 的引用:全局下变量,引用了 DOM对象。DOM对象 在document文档上移除了,但是这个全局变量还是保存着这个 DOM对象。
    • 闭包:全局下变量 引用 着 闭包函数。document对象也是全局变量。
  • 对循环引用的说明 【这种循环引用,现在浏览器可能已经做了优化处理了】         参考:https://www.cnblogs.com/greatluoluo/p/5930685.html 
    闭包中的循环引用指的是,闭包中引用了 包含函数的 对象。注意不是基本类型
    function leakMemory() {
        var el = document.getElementById('el');
        var obj= { 'el': el };
        el.obj= obj;
    }
    
    // 这段代码中(还不是闭包函数),函数中引用了外部的dom对象,同时, 函数中的 el.obj 给外部的dom对象添加了一个属性,引用了函数内的obj对象。
    //  这样就存在了相互引用的问题,js垃圾回收机制无法回收这个函数。

    我们常常像下面这样,在函数中给DOM对象创建事件,这样无意识中就存在内存泄漏问题。(个人觉得解决内存泄漏问题,可以把变量使用后设置为 null ,解除引用)

    function addHandler() {
        var el = document.getElementById('el');
        el.onclick = function() {
            el.style.backgroundColor = 'red';
        }
    }

    https://www.cnblogs.com/yakun/p/3932026.html      
    https://blog.csdn.net/johnny0991/article/details/51778436 (能导致内存泄漏的一定是引用类型的变量,而值类型的变量是不存在内存泄漏的)

五、chrome浏览器调试内存泄漏工具

  • F12控制台 - Performance  :看内存的 时间线
  • F12控制台 - Memory :保存内存快照,可以对比两次内存的差异对象

 


 

需要调用 API 释放内存的对象(而非设置null)

  • URL.createObjectURL() :这个API生成的对象,在内存中是会常驻的。需要手动释放内存的。
    释放内存:
    var objectURL = URL.createObjectURL(object);
    URL.revokeObjectURL(objectURL )   // 释放 内存
  • DOM 对象,JS中对某个DOM对象引用了。后面 document文档中删除了这个DOM,JS对这个DOM的引用也要断开。
    var button = document.getElementById('button'),
    
    document.body.remoyeChild(document.getElementById('button'))  // 这里document文档删除了DOM树上的这个DOM对象,但是js中还是保持者对它的引用,需要手动释放对他的引用
    button = null

 

 

 

 

 

 
posted @ 2018-05-20 22:20  吴飞ff  阅读(225)  评论(0编辑  收藏  举报