内存管理和内存泄露(闭包、作用域链)(二)https://developer.aliyun.com/article/1470366
闭包的执行过程
以下是我们已经非常熟悉的闭包过程,这次我们来看下他是怎么进行执行的,这次会解开我们之前还不了解的,为什么闭包会让本该执行完的执行上下文的自由变量不会被销毁掉
执行之前一样是非常熟悉的流程,直接上图啦
当foo开始执行之后:
当foo执行完了之后:这个时候,bar的内存地址已经存放到fn中了(也就是fn已经指向bar了),并且在后续被fn()给调用了,所以不管foo的函数执行上下文有没有被销毁,都不会影响到bar的函数对象了(因为GO根对象的fn已经指向了bar函数对象了上面有介绍JavaScript的垃圾回收,也就是标记清除部分
,让bar函数对象不被销毁),然后bar函数对象连锁反应又跟foo的AO对象相互进行引用了(最关键的是bar指向foo的AO对象,这是可达的部分),所以foo的AO对象也不会被销毁。这就是为什么bar引用的父级自由变量会得以保留的原因
我们接下来就要继续执行fn的函数执行上下文(bar的)了
当bar的执行上下文被销毁掉的时候,也不会影响闭包,因为根对象依旧指向着fn,也就是bar的函数对象,而bar函数对象的父级作用域parentScope指着foo的AO对象,所以脱离了捕捉时的上下文,它也能照常运行。自由变量依旧存在而没有被销毁
function foo(){ var name = "xiaoyu" var age = 20 function test(){ console.log("这是我的名字",name); console.log("这是我的年龄",age); } return test } var fn = foo() fn() //这是我的名字 xiaoyu //这是我的年龄 20
04_函数执行作用域链和深入闭包
闭包的内存泄漏
从上面的代码块中,我们可以知道,当bar函数不被销毁的时候,foo的AO对象就永远不会被销毁,因为我们bar要访问foo的AO对象里面的内容
- 目前因为在全局作用域下fn变量对0xb00的函数对象有引用,而0xb00的作用域中AO(0x200)有引用,所以会造成这些内存都是无法被释放的
但如果我们的bar函数只执行一次,后面就再也不需要了,那这个AO对象一直保存着就没有意义了,该销毁的却一直保留着,我们就叫这个是内存泄漏
闭包内存泄漏案例
- 只要arrayFns数组不被销毁,则createFnArray函数也会一直保留着不被销毁
V8引擎源码可以看到对数字的处理:(是在后面回顾的时候进行补充说明的)
function createFnArray(){ // 创建一个长度为1024*1024的数组,往里面每个位置填充1.观察占了多少的内存空间(int类型,整数1占4个字节byte) //4byte*1024=4kb,再*1024为4mb,占据的空间是4M × 100 + 其他的内存 = 400M+ //在js里面不管是整数类型还是浮点数类型,看起来都是数字类型,这个时候占据的都是8字节,但是js引擎为了提高空间的利用率,对很多小的数字是用不到8个字节(byte)的,8字节 = 2的64次方,所以8字节是很大的,现在的js引擎大多数都会进行优化,对小的数字类型,在V8中称为Smi,小数字 2的32次方 var arr = new Array(1024*1024).fill(1) return function(){ console.log(arr.length); } } //var arrayFn = createFnArray() //arrayFn() var arrayFns = [] for(var i = 0 ; i<100 ; i++){ //createFnArray()//我们通过for循环不断调用createFnArray这个函数,我们没有使用任何函数去接收他,所以当他创建进入下一个循环之后就会马上被销毁掉 arrayFns.push(createFnArray()) }
内存泄漏解决方法
//内存泄漏解决方法 function foo(){ var name = "xiaoyu" var age = 20 function test(){ console.log("这是我的名字",name); console.log("这是我的年龄",age); } return test } var fn = foo() fn() fn = null//将fn指向null,null的内存地址为0x0。此时fn指向bar的指针就会断开了,AO对象跟bar函数对象就形成了一个对于根对象的不可达的对象,将再下次被销毁掉。注意,你把它置为null之后,不会马上回收的,会在发现之后的下一轮进行回收
AO不使用的属性
- 我们来研究一个问题:AO对象不会被销毁时,是否里面的所有属性都不会被释放?
- 下面代码中的name属于闭包的父作用域里面的变量
- 我们知道形成闭包之后count一定不会被销毁掉,那么name是否会被销毁掉呢?会,没有被使用到的会销毁掉,
V8引擎做的优化
function makeAdder(count){ let name ="why" return function (num){ debugger return count + num } } const add10 = makeAdder(10) console.log(add10(5)); console.log(add10(8)); //15 //18
闭包的内存泄漏测试
内存回收案例测试如下
如果我们连foo函数对象都不想要了,我们也来个foo = null,断掉了foo与根对象GO的联系,那下次foo函数也会被销毁,或者说垃圾回收掉
回收一半的内存
JS闭包引用的自由变量销毁
当我们除了声明了fn来接收foo()之外,又声明了baz同样子接收foo(),这个时候是又执行了一遍foo函数里面的bar部分,fn跟baz不是同时指向同一个地方,而是又创建了一个新的foo的AO对象跟bar的函数对象,当我们将fn指向null,将内存进行回收时的时候,销毁的也只是fn对应的bar函数对象跟foo()对象,而对baz产生的bar函数对象跟foo的AO对象没有任何的影响,毕竟baz是又重新走了一遍流程,baz跟fn是互相独立的(PS:foo的AO对象是由bar的父级作用域内存地址指向而产生出来的)
- foo的AO对象有bar在指向着,因为bar函数内含父级作用域foo的AO对象的内存地址且正处于引用状态,这个内存地址指向着AO对象,让AO对象不会被销毁掉,但是我们只是引用name这个自由变量,age并没有使用到,按照ECMA规范,正规AO对象都不会被销毁,当然也就包含了我们没有用上的age变量了
- 但是js引擎是非常灵活的,为了提高内存的利用率,这个可能永远使用不上的age属性是会被回收掉的,从而提高空余的内存空间,提高性能
闭包引用的AO对象属性销毁
通过debugger我们可以看到未使用的父级作用域的变量会被js引擎回收掉,如果引用了则不会
function foo(){ var name = "why" var age = 18 function bar(){ debugger console.log(name) } return bar } var fn = foo() fn()