垃圾回收机制与内存管理

欢迎访问我的博客https://qqqww.com,祝码农同胞们早日走上人生巅峰,迎娶白富美~~~
Js具有自动垃圾回收机制。垃圾收集器会按照固定的时间间隔周期性的执行。
什么是垃圾回收机制
垃圾回收机制(GC:Garbage Collection):
JavaScript具有自动垃圾收集机制,也就是说,执行环境会负责管理代码在执行环境过程中使用的内存。
垃圾收集器会按照固定的时间间隔(或代码执行中预定的收集时间),周期性的执行这一操作。
垃圾回收机制的作用
【垃圾回收机制与内存管理】内存释放:
  1. 将某个不再被使用的变量所占用的内存释放掉,是为了不让内存总是被消耗而被没有释放,导致整个系统无法支持更为庞大的运算
  2. 垃圾回收机制不是总是那么高效,其不是总在运行,因此有时需要我们人为的释放被占用的内存,从而使程序能够支持更为庞大的运算,能够接受更为巨量的数据运算。
垃圾回收机制的原理
垃圾收集机制的原理:
找出那些不再继续使用的变量,然后释放其占用的内存,但是这个过程不是实时的,因为其开销比较大,所以垃圾回收器会按照固定的时间间隔周期性的执行
实例
function fn1 () { var obj = { name: 'zhangsan', age: 10 } } function fn2 () { var obj = { name: 'lisi', age: 11 } return obj } var a = fn1() var b = fn2()

fn1中定义的obj为局部变量,而当调用结束后,出了fn1的环境,那么该块内存会被js引擎中的垃圾回收器自动释放;在fn2被调用的过程中,返回的对象被全局变量b所指向,所以该块内存并不会被释放
垃圾回收机制的标记策略
垃圾收集器必须跟踪哪个变量有用哪个变量没用,对于不再有用的变量打上标记,以备将来收回其占用的内存。用于标识无用变量的策略可能会因实现而异,但具体到浏览器中的实现,则通常有两个策略:标记清除、引用计数。
标记清除
1. 当变量进入环境时,将其标记为“进入环境”,当变量离开环境时,将其标记为“离开环境”(常用)
2. 某一个时刻,垃圾回收器会过滤掉环境中的变量,以及被环境变量引用的变量,剩下的就是被视为准备回收的变量
3. 到目前为止,IE、Firefox、Opera、Chrome、Safari的js实现使用的都是标记清除的垃圾回收策略或类似的策略,只不过垃圾收集的时间间隔互不相同
引用计数
跟踪记录每个值被引用的次数
  1. 当声明一个变量a并将一个引用类型的值赋给该变量时,这个值引用次数就是1
  2. 如果同一个引用类型的值又被赋给另一个变量b,则该值的引用次数加1,相反,如果包含这个值引用的变量a又取得了另一个值(也就是前面的引用被覆盖,不再引用前面那个值),则这个值的引用次数减1。
  3. 当这个值的引用次数变成0时,则说明没有办法再访问这个值,因而就可以将其占用的内存空间回收回来
  4. 少见,几乎不用,除了早版IE的元素JS,因为会出现循环引用的问题
`var` `a = obj1; ``var` `b = boj1; ``//这是obj1的引用次数应该为2``var` `a = obj2; ``//这是obj1的引用次数为1,obj2的引用次数也为1`

垃圾回收机制与内存管理
垃圾回收器是周期运行的,而且如果为变量分配的内存数量很可观,那么回收工作量也是相当大的。触发方式的改善很重要
堆和栈
  1. 堆和栈空间分配区别:
    1. 栈(操作系统):由操作系统自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈
    2. 堆(操作系统): 一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收,分配方式倒是类似于链表
  2. 堆和栈空间存放数据类型的区别:
    1. 栈:基本类型是:Undefined/Null/Boolean/Number/String,基本类型的值存在内存中,被保存在栈内存中。从一个变量向另一个变量复制基本类型的值,会创建这个值的一个副
    2. 堆:引用类型:object,Array,function等,引用类型的值是对象类型,保存在堆内存中
      1. 包含引用类型值的变量实际上包含的并不是对象本身,而是一个指向该对象的指针。从一个变量向另一个变量复制引用类型的值,复制的其实是指针,因此两个变量最终都指向同一个对象
      2. js操作对象就是在操作对象的引用,而不是在操作实际的对象
  3. 堆和栈缓存方式区别:
    1. 栈使用的是一级缓存, 他们通常都是被调用时处于存储空间中,调用完毕立即释放
    2. 堆是存放在二级缓存中,生命周期由虚拟机的垃圾回收算法来决定(并不是一旦成为孤儿对象就能被回收)。所以调用这些对象的速度要相对来得低一些
  4. 堆和栈数据结构区别:
    1. 堆(数据结构):堆可以被看成是一棵树,如:堆排序
    2. 栈(数据结构):一种先进后出的数据结构
管理内存
虽然说使用具备垃圾回收机制的语言写程序,开发人员一般不必操心内存管理的问题。但是JavaScript在进行内存管理及垃圾收集时面临的问题还是有点与众不同的,其中最主要的问题之一,就是出于对安全方面考虑,防止运行JavaScript的网页耗尽全部系统内存而导致系统崩溃,所以分配给Web浏览器的内存数量通常比分配给桌面应用的程序少
内存泄漏
虽然有垃圾回收机制但是我们编写代码操作不当还是会造成内存泄漏
那么什么情况会引起内存泄漏呢?
  1. 意外的全局变量引起的内存泄漏
    function fn () { fn1 = 'xxxxxx'; //fn 成为一个全局变量,不会被回收 }

    1. 原因:全局变量不会被回收
    2. 解决:使用严格模式(因为严格模式定义的变量都有其单独的作用域)
  2. 闭包引起的内存泄漏
    var fn = (function(){ var fn1 = 'xxxxxx'; // 被闭包所引用,不会被回收 return function(){ console.log(fn1); } })()

    1. 原因:闭包可以维持函数内部局部变量,使其得不到释放
    2. 解决:将事件处理函数定义在外部,解除闭包,或者在定义事件处理函数的外部函数中,删除对DOM的引用
  3. DOM清空或删除时,事件未清除导致的内存泄漏
    案例一:

    $('#container').bind('click', function(){ console.log('click'); }).remove();

    案例二:

    var btn = document.getElementById("myBtn"); btn.onclick = function(){ document.getElementById("myDiv").innerHTML = "Processing..."; }

    1. 原因:虽然DOM被清空或者删除了,但是对象中还存在对DOM的引用
    2. 解决一:zepto 和 原生 js 下,#container dom 元素,还在内存里 jquery 的 empty 和 remove 会帮助开发者避免这个问题
      $('#container').bind('click', function(){ console.log('click'); }).off('click').remove();

    3. 解决二:手动删除
      var btn = document.getElementById("myBtn"); btn.onclick = function(){ btn.onclick = null; document.getElementById("myDiv").innerHTML = "Processing..."; }

  4. 子元素存在引用引起的内存泄漏
    [图片上传失败...(image-99c20a-1548520439758)]
    1. 原因:上图
      1. 黄色是指直接被 js变量所引用,在内存里
      2. 红色是指间接被 js变量所引用,如上图,refB 被 refA 间接引用,导致即使 refB 变量被清空,也是不会被回收的
      3. 子元素 refB 由于 parentNode 的间接引用,只要它不被删除,它所有的父元素(图中红色部分)都不会被删除
    2. 解决:手动删除
  5. 被遗忘的定时器或者回调引起的内存泄漏
    1. 原因:定时器中有dom的引用,即使dom删除了,但是定时器还在,所以内存中还是有这个dom
    2. 解决:手动删除定时器
总结
  1. 占用最少的内存可以让页面获得更好的性能,而优化内存占用最佳方式,就是执行中的代码只保留必要的数据(占用必要的内存)。
  2. 一旦数据不再有用,最好通过将其值设置为null来释放其引用——这个做法叫做解除引用。这一做法适用于大多数全局变量和全局对象的属性。局部变量会在它们离开执行环境时自动解除引用。
  3. 解除一个值的引用并不意味着自动回收该值所占用的内存。解除引用的真正作用是让值脱离执行环境,以便垃圾回收器下次运行时将其回收
function getPerson (name) { var privatePerson = new Object() privatePerson.name = name return privatePerson } var globalPerson = getPerson('zhangsan')// 此时调用了函数getPerson,使得返回的对象privatePerson会被全局变量globalPerson所指向,所以该块内存并不会被释放,可以手动`globalPerson = null`来释放内存

    推荐阅读