1.fp会在程序开始执行时向系统申请内存,每次申请4096字节,然后在其内部以512字节或256字节分隔存放。当程序实例化对象时即会向fp请求,不管此对象引用是否为0,在下次内存回收触发前,这部分内存都不会被清除,也就是占用状态。
2.何时会触发内存回收呢?当程序向fp申请内存而空间不足时,回收即被触发。符合回收规则的对象被清除,然后碎片整理,然后再进行分配。可以看出,这是一个很耗费CPU的过程。当fp内存即将耗尽时,如果频繁进行内存请求,就会不断触发回收,然后CPU飙升,卡屏掉帧。解决的思路就是使用对象池技术,大段申请空间。对于频繁使用的对象,考虑不清除,长驻内存。大块的同类型对象可以使用vector
3.什么是符合回收规则的对象?fp会使用两种方法去判定,即引用计数法和标记清除法。
(1)引用计数法:对象添加了一个引用,计数器加1,删除时减1.当执行内存回收时,计数器为0的对象将被回收。
(2)标记清除法:当两个对象互相引用时,计数器始终为1,引用计数法就进入了死循环的胡同。那么标记清除实质上是从应用程序根对象也就是root开始对每一个引用的对象作标识,遍历后发现没有标识的对象即可以清除,这样互相引用的对象和外界节点已经不存在引用,因此会被一并清除。
4.什么是内存泄露?从上面的描述中可以看出,由于失误导致fp无法清除不再使用的内存,也就是你希望被回收的内存并没有满足回收规则,最终导致程序占用内存越来越大,即算是内在泄露。
5.我要遵守什么法则?
事实上,严格遵守引用计数法会很累,代码也会因为到处都是置null而变得繁琐。我们可以依据标记清除法来偷懒:
(1)一个对象维持着它的若干属性,当此对象与根节点无关时,它的属性自然也变成无关,这样就会被一并回收。因此不需要将一个对象所有属性置为null才让其被回收。同样,对于容器和子对象的关系,也并不需要removeChild所有的子对象。
(2)静态属性是一个特殊的情况。静态属性本身就是根,所以你必须将其设置null才有可能被回收,没有别的办法。
(3)A.addEventListener("event",B.handler);我们可以理解为B的handler变成A的一个属性引用了。当A和B是同一个对象即自己侦听自己的事件时,或者A是B的属性或子child时,根据上述规则,是会被回收的。但当A是B的父对象,如parent或stage,那么由于A一直不会回收,就会导致成为A属性的B.handler也不会回收,这时必须调用A.removeListener将B.handler从A中清除。
6.特别注意位图的回收
在销毁一个图像容器后,注意其bitmapdata需要调用dispose方法才会从内存中清除。这也是在出现内存泄露时,首个要排查的因素,因为它占用的体积相当大。
7.弱引用是什么?
弱引用会改变垃圾回收的规则。如果使用了弱引用,addEventListener将不会影响对象回收,即使对stage添加监听,也不会导致自己被回收。但是这同时也是缺点,因为有的时候你就是希望用引用限制住对象的回收,使用弱引用会使得这个对象有时回收有时不回收。虽然极少出现,但一旦出现,这种不容易重现的错误是很难查出来的。因此我并不推荐使用弱引用。
弱引用在AVM2中只有两处:
一处是addEventListener的第5个属性,名为userWeakReference,设置为true,监听事件将不会影响对象回收。
一处是Dictionary的构造函数参数,名为weakKeys,设置为true,当键为复杂对象时,即使Dictionary存在,键依然可以被回收。注意,这里说的是键,不是值,值是不享受弱引用待遇的。这个属性也写得也很明白,是weakKeys。
这两处默认值都为false
8.我能主动控制GC么?
System.gc()只适用于调试版的FP,为避免滥用,发布版无法使用此函数。但在一些特殊时机,比如切换屏幕,加载资源时,也就是不怕掉帧的时候,可以顺便GC一下。这时候就需要一个HACK方法来强制GC了,原理很简单,大量申请内存,强迫FP去GC,然后再释放掉:
try {
new LocalConnection ().connect ( "gc" );
new LocalConnection ().connect ( "gc" );
} catch ( e:Error ) {}
9.确定内存泄露的好工具FlashBuilder Profiler概要分析
菜单中的运行->概要分析: