JAVAScript 具有自动垃圾收集机制,执行环境会负责管理代码执行过程中使用的内存。在编写JS的过程中,开发者不用关心内存的问题,所需内存的分配以及无 用内存的回收完全实现了自动管理。这种垃圾收集机制的原理其实很简单:找出那些不再继续使用的变 量,然后释放其占用的内存。为此,垃圾收集器会按照固定的时间间隔(或代码执行中预定的收集时间), 周期性地执行这一操作。
内存分配:局部变量只在函数执行的过程中存在。而在这个过程中,会为局部变量在栈(或堆)内存上分配相应的空间,以便存储它们的值。
内存使用:然后在函数中使用这些变量,直至函数执行结束。
内存回收:此时,局部变量就没有存在的必要了,因此可以释放它们的内存以供将来使用
JS 中最常用的垃圾收集方式是标记清除(mark-and-sweep) ,当变量进入环境(例如,在函数中声明一个变量)时,就将这个变量标记为“进入环境”。从逻辑上讲,永远不能释放进入环境的变 量所占用的内存,因为只要执行流进入相应的环境,就可能会用到它们。而当变量离开环境时,则将其 标记为“离开环境”。
由于被清理完的内存是不连续的,所以导致了后续存量大的对象可能无法正常存入内存当中,一般的处理方式都是在垃圾回收后进行整理操作,这种方法也叫 标记整理,整理的过程就是将不连续的内存向一端复制,使不连续的内存连续起来。目前主流浏览器的 JS 实现使用的都是 标记清除 式的垃圾收集策略(或类似的策略),只不过垃圾收集的时间间隔互有不同
举个 funtion example(){ // 此时被标记,进入环境 let a = 10 }; example(); // 函数执行完毕,此时a被标离开环境,被回收。 // 垃圾收集器 完成内存清除工作,销毁那些带标记的值并回收它们所占用的内存空间
引用计数的含义是跟踪记录每 个值被引用的次数。当声明了一个变量并将一个引用类型值赋给该变量时,则这个值的引用次数就是 1。 如果同一个值又被赋给另一个变量,则该值的引用次数加 1。相反,如果包含对这个值引用的变量又取 得了另外一个值,则这个值的引用次数减 1。当这个值的引用次数变成 0 时,则说明没有办法再访问这 个值了,因而就可以将其占用的内存空间回收回来。这样,当垃圾收集器下次再运行时,它就会释放那 些引用次数为零的值所占用的内存
引用计数图例
举个 function example(){ let a = 1; // a 初始化 count = 0 let b = a; // b引用a,count = 1 let b = 1; // b接触对a的引用,count = 0。当count重新为0时,内存回收a }
执行中的代码只保存必要的数据。一旦数据不再有用,最好通过将其值设置为 null 来释放其引用——这个 做法叫做解除引用(dereferencing)。这一做法适用于大多数全局变量和全局对象的属性。局部变量会在 它们离开执行环境时自动被解除引用
举个 function example(name){ let localPerson = new Object(); localPerson.name = name; } var globalPerson = example("Nicholas"); // 手工解除 globalPerson 的引用 globalPerson = null; // 解除globalPerson引用,让值脱离执行环境,垃圾收集器下次运行时便会将其回收
原因:一个未声明的变量的引用在全局对象中创建了一个新变量。在浏览器的环境中,全局对象是window 解决办法:添加'use strict'
原因:在js的内存管理环境中,对象 A 如果有访问对象 B 的权限,叫做对象 A 引用对象 B。引用计数的策略是将“对象是否不再需要”简化成“对象有没有其他对象引用到它”,如果没有对象引用这个对象,那么这个对象将会被回收 。 举个 function example() { let obj1 = {}; let obj2 = {}; obj1.a = obj2; // obj1 引用 obj2 obj2.a = obj1; // obj2 引用 obj1 } 当函数 example 执行结束后,返回值为 undefined,所以整个函数以及内部的变量都应该被回收,但根据引用计数方法,obj1 和 obj2 的引用次数都不为 0,所以他们不会进入回收队列 解决方案:不使用时设为null
闭包会造成对象引用的生命周期脱离当前函数的上下文,如果闭包如果使用不当,可以导致环形引用(circular reference),类似于死锁,只能避免,无法发生之后解决,即使有垃圾回收也还是会内存泄露。
原因:使用setInterval/setTimeout ,使用完之后通常忘记清理 解决办法:clearInterval/clearTimeout
这个和 Java 回收策略思想是一致的。目的是通过区分「临时」与「持久」对象;多回收「临时对象区」(young generation),少回收「持久对象区」(tenured generation),减少每次需遍历的对象,从而减少每次GC的耗时。Chrome 浏览器所使用的 V8 引擎就是采用的分代回收策略。
分代回收示例图
这个方案的思想很简单,就是「每次处理一点,下次再处理一点,如此类推」。这种方案,虽然耗时短,但中断较多,带来了上下文切换频繁的问题。Firefox 浏览器所使用的 JavaScript 引擎就是采用的增量回收策略。
增量回收示例图
内存优化引用:https://juejin.im/entry/58650be8ac502e005ff7b1e3