简介
基础数据是存储在栈中的,如number,string,而对象数据是存储在堆中,通过栈内一个指针指向堆内存中
即 var a = b = {},其实只会分配一个对象的内存
而这个对象一旦不使用了,就需要被释放
回收原理
var a = b = {};
这个对象引用计数为2,一旦 a = b = null,这时候这个对象再也无法访问到了,就被垃圾回收了
复合引用
同样 var a = {b: {}},会创建两个对象,但是一旦 a = null,内部b的那个对象也无法访问到了,进而被回收了
egret的案例
在egret官网会看到很多例子
this.addEventListener(egret.Event.REMOVE_FROM_STAGE, this.dispose, this);
用来做一个销毁的钩子函数,我们不禁想:(这个事件本身不需释放吗?)
参考事件的机制,实际上事件就是给把所有callback给注册到数组里面,但是这个数组还是当前的this,因为显示对象默认继承 egret.EventDispatcher,当前this的对象访问不到,这数组的自然也访问不到了
不过不太建议使用这个方法,因为有时候我们仅仅setChildIndex也会触发此方法,应该有一个统一的destroy的钩子,外部驱动
因此内部事件通常不需要考虑释放,外部事件需要,因为外部事件需要劫持当前对象的this,如外部EventCenter的监听
Promise的案例
Promise通常用于一些异步的场景,代表在一定时间之后,会触发的一些行为的对象,但是如果中途clearTimeout等终止操作,是否导致一直pending是否内存泄漏呢?
经过测试,其实Promise内部的异步处理逻辑通常都是外部驱动的,如setTimeout,此时会把闭包内的resolve方法传递给了外部驱动的队列中,外部持有闭包内的对象,导致拥有这个域的Promise对象不会被回收(尽管我们不需保存promise的引用),一旦我们手动clearTimeout,Promise内部变量不再被外部劫持,虽然无法resolve,但也会被自动回收了
常见的情形是我们会存在多分支干扰的情形,如游戏流程会有一个总的timeout计时器,如果我们在await一个开场动画之后需要生成棋盘,但是此时timeout,其实我们可以stop开场动画,同时这个promise还会被回收,即await不一定一定需要reject来改变流程,流程是可中断的
内存泄漏排查
- 我们可以通过 chrome 的 Memory 做一些比对,查看堆内存中对象的增量来判定
- 经过测试,我们甚至可以查看一个对象的引用关系!也许我们能够进一步访问[[scope]]那些私有化的变量?
- 通常而言,平时开发我们泄漏点主要在数组,因为一个对象属性最多一次只会劫持一个obj的引用,但是数组可以无限劫持!如龙骨的世界钟数组,事件中心的callback数组,缓动,定时器的全局驱动数组
