垃圾回收,这次是一个被无数人讨论过的传统话题。
Action Script使用的是和Java相似的内存管理机制,并不会即时回收废弃对象的内存,而是在特定时间统一执行一次GC(Gabage Collection)操作来释放废弃对象的内存,避免了重复判断是否需要回收产生的性能问题。
但要注意,这只是决定回收的时机,而不是回收的内容。这个延迟执行内存回收也就是个表面的现象,不管什么时候执行GC,能够回收的内存最终都能回 收,不能回收的肯定不能回收。唯一的影响是,因为回收是延迟执行的,你在查看内存的时候不能直观地看到因为一个对象被废弃而回收内存的过程,会产生迷惑。
但这对于解决内存泄露是无关紧要的。
内存泄露指的就是当你销毁了一个对象的时候,它占用的内存却无法被回收,这会导致可用内存越来越小最终溢出,在内存紧张的环境中将会造成系统崩溃。其原因多种多样,但一般都是开发者的疏忽所致,没有提供给系统足够的可以销毁对象的依据。Android应用排行榜
执行GC虽然和内存泄露没有关系,但是如果不在测试前执行GC,你将看不到当时实际的不可回收内存的量,而内存泄露就是指不可回收内存的数量的增 加。因此,测试内存回收将离不开GC方法。没有使用GC方法的测试用例是没有意义的,因为这其中掺杂了偶然性(什么时候执行GC)。不少荒谬的测试结果都 是因为没有在正确的位置执行GC导致的。
Flash Player虽然没有开放发布状态的手动gc,但调试版本是可以使用的,正好可以让我们测试。此外下面的HACK代码也可以在发布阶段触发GC。
try { new LocalConnection ().connect ( "gc" ); new LocalConnection ().connect ( "gc" ); } catch ( e:Error ) {}
但我再次强调,调用GC仅仅是用于测试。实际产品中调用GC基本没有意义(除了用于控制GC时机),总之如果你的程序出现了内存泄露,那一定和GC没有关系,请不要再在这种地方浪费宝贵的时间与精力。
AVM2的GC是在每次申请内存时,根据当前内存占用来触发的。申请内存是一个必要因素。所以,如果你一直不进行申请内存的操作,就算内存达到了一个高值,它也不会进行GC。
这确实是个不合理的地方。但是,在实际环境中,一直不请求内存的情况是很少见的,就算出现,当时也未必处于内存的高值。这种情况主要出现在测试环境中,导致一些人会怀疑自动GC的功能是否正常。实际上这也是没有必要的。
在AVM2中,除去特殊的BitmapData必须调用dispose才能回收内存外,其他的部分都是用引用计数法和标记清除法作为判断是否应该回收内存的手段,而且并没有提供主动回收的API,详细部分请看这篇日志,我就不重复了。
因此,你要回收一个对象,只要保证没有任何对象引用它,而且他的方法没有被当做事件函数——或者说,他和程序的其他部分已经没有任何联系,它就满足 了引用计数法的标准,就一定会被回收。做到这一点的方法就是一般说的“执行removeChild,removeEventListener,将对他的引 用设置为null”。
但是,实际上回收一个对象的要求并没有那样严格,就在于FP除了引用计数法,还包括标记清除法。标记清除法是从程序的根对象开始(stage,静态 属性,活动的定时器和加载器,ExternalInface.callBack)一级一级遍历对象,只要遍历不到,即使不满足引用计数法的条件也可以回 收。比如两个对象互相引用,但是和外界都没有关系,形成了孤岛,它们就可以被回收,尽管它们因为互相引用使得引用数不为0。比起单纯的引用计数,这种办法 能确实能找到已经无法再访问到的实际上的闲置对象。所以,可以看到很多人的代码实际上并没有设置null,甚至没有 removeEventListener,它一样可以被正常回收,少写这些代码可以使得程序更简洁,要全部符合标记清除法的条件,会很累。
“无法被根访问”,这种说法很暧昧,基本不能当做判断依据。所以我下面会举几个具体例子,来说明什么样的情况是符合标记清除法的要求的。坏账
首先明确一点,标记清除法是只以能否能被根访问作为唯一依据的,并不需要关注被引用的次数,请不要混淆。
总得来说,就是务必注意对stage,parent的事件监听,其他情况一般都是不会妨碍回收的。而对stage,parent的监听大多都是各种鼠标,键盘事件。数量并不多,专门注意这里可以杜绝大部分因为事件造成的内存泄露。
其实,内存泄露并不容易出现。按照普通的编程习惯,只有监听stage事件这种做法会造成意料之外的泄露,一般都是可以顺利回收的。这比每次都要手工回收内存要方便多了。2011大智慧下载
这里只有BitmapData是例外。除了遵从上面的规则外,要回收它的内存,必须手动调用dispose方法,习惯自动回收的人会很累。务必注 意,Bitmap对象的bitmapData属性是需要手动销毁的,Loader加载的位图是需要手动销毁的,当你用一个生成的位图作为位图填充绘制平铺 的图像后,在销毁这个图像后也必须销毁这个位图(所以你必须一直保存位图的引用)。BitmapData是32位的未经任何压缩的图像,随便一个体积都会 非常大,不处理好它们的回收,一个BitmapData泄露就可以顶你数万个复杂对象的泄露。
如果出现非常明显的内存泄露,大部分时候都是位图泄露。所以在研究上面的引用计数法和标记清除法以及GC之前,请先保证位图部分不出问题。
弱引用会改变垃圾回收的规则。如果使用了弱引用,addEventListener将不会影响对象回收,即使对stage添加监听,也不会导致自己 被回收。但是这同时也是缺点,因为有的时候你就是希望用引用限制住对象的回收,使用弱引用会使得这个对象有时回收有时不回收。虽然极少出现,但一旦出现, 这种不容易重现的错误是很难查出来的。因此我并不推荐使用弱引用。
弱引用在AVM2中只有两处:
因为每次GC都需要消耗性能,对象越多,GC越慢。我理解Flash Player禁用发布版本的System.gc()是为了避免开发者滥用这个方法,但有些时候我们的确需要手动控制GC时机,因为GC过程如果遇到大量可回收对象会让Flash Player卡住。
比如,我们需要在切换屏幕时回收一次内存,这时候卡是看不出来的,而不是切换完后播放动画时回收然后让动画顿住。或者,我们会定期在必要的时候执行 一次GC,将GC需要的时间分担开。所以这时候用HACK方法强制执行一次GC也不失为一个选择。当然,这和内存泄露半点关系都没有。
Flash Player这个地方的设计特别的不好。它自己又不支持分步GC,一旦GC的时候没有办法避免卡的问题。结果GC的时机还不给控制……
测试中FLASH的确存在微量内存无限增加的问题,原因未知。我将50万个对象扔在一个数组中,销毁后确实会多出1M的内存占用(如果没扔在数组中不会),但这个数量很小,但达到能看得出来的100M内存需要5000万个对象,这个数额在通常情况下很难达到。
不过也有人说这只是对象销毁而内存并未全部释放的表现,实际上最后还是能完全释放的。或者是因为totalMemory的不精确所造成的。这个我就不清楚了。
不过就算这个的确是FlashPlayer的BUG,也无伤大雅吧。