Flex内存使用技巧
内存问题向来是程序员十分关注的一个方面,也是体现程序健壮性与否.对于C/C++等类C语言来说,编程人员要对程序使用的内存进行比较谨慎的处理,否则很容易导致内存泄露而使得程序运行缓慢甚至失效.而对于Java等面向对象语言来说,由于采用了一种叫做garbage collection的内存管理机制,极大地将程序员从繁琐的内存管理中解脱出来.但我们不应掉以轻心,如果只是一味的一来这种机制,会使得程序执行效率低下,甚至存在极大的隐患.本文试图从ActionScript的角度来介绍如何正确而高效地处理内存问题,试图给大家提供一种高效率构建富客户端程序的方法.
Flash Player(本身就是一个AVM,AS虚拟机)垃圾回收工作是由垃圾回收器完成的。垃圾回收器是运行在后台的一个进程,它释放那些不再被应用所使用对象所占用的内存。不再被应用所使用的对象是指那些不再会被那些活动着(工作着)的对象所“引用”的对象。在 AS 中,
对于非基本类型(Boolean, String, Number, uint, int)的对象,在对象之间传递的都是对象引用,而不是对象本身。删除一个变量只是删除了对象的引用,而不是删除对象本身。一个对象可以被多处引用,通过这些不同的引用所操作的都是同一个对象。如下图:
虽然将O1设置成null了,但内存中它所指向的实例可能并未删除.这取决于垃圾回收器的运行情况.还有一个比较典型的例子是当你使用addChild()往容器中添加了一定数量的可视组件后,又打算使用removeChild()方法来移除容器,虽然从界面上似乎看到可视组件被“删除”了,然而一切并非如你所愿,这些可视组件仍然位于内存中,明显出现了严重的内存泄露,明明发生了我们却置若罔闻!
为此我打算从以下几个方面来细致讨论ActionScript的内存问题:
一.AVM垃圾回收机制
对于非基本类型的对象(基本类型包括Boolean,string,number,int,uint),AVM采用了两种方式来判定一个对象是否是活动的引用,以此确定是否应对其内存进行回收
(1) 引用计数法:这是一种简单却比较有效地方式。当创建一个对对象的引用后,对象的引用计数加1,而删除一个引用后,对象的引用计数减1。如果对象的引用计数变为0时,垃圾回收器可以将这个对象从内存中删除。但是当对象间出现了循环引用的情况后,尽管彼此已经不再使用这些对象了,但是它们的引用计数仍然大于大于0,因此无法回收。
(2) 标记清除法:AVM从应用的根节点开始,遍历所有在其上的引用,标记每个它所发现的对象。然后迭代遍历每个被标记的对象,标记它们的子对象。过程结束后,如果内存中出现了没有被标记的对象时,表示该对象已经没有任何活动引用了,因此可以安全将它们“粉碎”。
标记清除机制非常精准,但是这种递归执行大大增加了CPU负荷。所以AVM为了减少这种开销只是在需要的时候偶尔执行这种方式来查找无效对象。
注意:上面所说的引用指的是“强引用(strong reference)”,flash player 在标记清除过程中会忽略“弱引用(weakness reference )”,也就是说,弱引用在标记清除过程
不被当做引用,不会阻止垃圾回收。
默认情况下,AVM执行垃圾回收发生在Flash Player需要另外请求内存之前。也就是只有Flash占用内存紧张到一定程度时,才会触发真正的垃圾回收。我们需要明确两点:A何时真正执行垃圾回收不可预知;B垃圾回收总是在内存吃紧时触发,而非删除对象时。
二.内存泄露常见情况
我们在开发中经常会无意识犯以下内存泄露错误:
A. 被全局对象所引用的对象在不需再次使用后却忘记从全局对象中清除对它们的引用。常见的全局对象有 stage,主Application,类的静态成员等。
B. 无限次触发的Timer会导致内存泄露。。 无论无限次触发的 Timer 是否为全局对象,无限次触发的 Timer 本身以及注册在Timer 中的监听器对象都不会被垃圾回收。
C. 通过隐式方式建立的对象之间的引用关系更容易被程序员所忽略,从而导致内存泄露。 最常见的以隐式方式建立对象之间的引用就是“绑定”和“为对象添加事件监听器”。通过测试我们发现“绑定”不会造成内存泄露,对象可以放心地绑定全局对象。而调用addEventListener()方法“为对象添加事件监听器”则可能产生内存泄露,大多数内存泄露都因此而来。
对于解决addEventListener方法可能造成的内存泄露问题,以下情况来使用addEventlistener不会发生:
1. 用弱引用方式注册监听器。 就是调用时将addEventListener的第五个参数置为true,
例如:someObject.addEventListener(MouseClick.CLICK, otherObject.handlerFunction, false, 0, true);
2. 自引用的方式。即:为对象添加的监听处理函数是对象本身的方法。例如:
this.addEventListener(MouseClick.CLICK, this. handlerFunction);
3 子对象引用。即:为子对象添加的监听处理函数是父上对象的方法。例如:
private var childObject:UIComponent = new UIComponent;
addChild(childObject);
childObject.addEventListener(MouseEvent.CLICK, this.clickHandler);
三.内存使用优化建议
1. 被删除对象在外部的所有引用一定要被删除干净才能被系统当成垃圾回收处理掉;
2. 父对象内部的子对象被外部其他对象引用了,会导致此子对象不会被删除,子对象 不会被删除又会导致了父对象不会被删除;
3. 如果一个对象中引用了外部对象,当自己被删除或者不需要使用此引用对象时,一定要记得把此对象的引用设置为null;
4. 本对象删除不了的原因不一定是自己被引用了,也有可能是自己的孩子被外部引用了,孩子删不掉导致父亲也删不掉;
5. 除了引用需要删除外,系统组件或者全局工具、管理类如果提供了卸载方法的就一定要调用删除内部对象, 否则有可能会造成内存泄露和性能损失;
6. 父对象立刻被删除了不代表子对象就会被删除或立刻被删除,可能会在后期被系统自动删除或第二次移除操作时被删除;
7. 如果父对象remove了子对象后没有清除对子对象的引用,子对象一样是不能被删除的,父对象也不能被删除;
8. 注册的事件如果没有被移除不影响自定义的强行回收机制,但有可能会影响正常的回收机制,所以最好是做到注册的事件监听器都要记得移除干净。
9. 父对象被删除了不代表其余子对象都删除了,找到一种状态的泄露代码不等于其他状态就没有泄露了,要各模块各状态逐个进行测试分析,直到测试任何状态下都能删除整个对象为止。
四.自定义强制垃圾回收类
所谓强制执行垃圾回收是指通过故意使swf在运行时出错,然后throw出错误,而同时通过catch error来继续运行swf文件。而垃圾回收则会在swf抛出错误的时候,被强制执行一次,以清除内存中无效的数据占用,减少资源的消耗。不过并不是所有的error throw都能触发垃圾回收,而也只局限于某些特定的error,经验发现使用mulinrj参数是比较有效的。
/*
Programmer: Eric Huang
Build Date: 2010-07-21
Version: v1.0
Summary: to do garbage recycling work forcefully
Copyright (C) 2010, MetarNet Technologies Co., Ltd.
All Rights Reserved
*/
package com.metarnet.controls
{
import flash.net.LocalConnection;
import flash.system.System;
public final class Memory
{
public function Memory()
{
//TO DO
}
public static function gc():void
{
try
{
new LocalConnection().connect('"mulinrj');
new LocalConnection().connect('"mulinrj');
}
catch (e:*)
{
}
}
public static function get used():Number
{
return System.totalMemory;
}
}
}
五.Flex Builder3 的内存泄露分析工具
Flex Builder3 Pro 带有一个"剖析(Profiler)"工具可以用来帮助我们识别内存
泄露。在 Flex Builder3 Pro 的中选择要被"剖析(Profiler)"的应用后点击鼠标右键,
在右键菜单中选择“Profile As” 就可以运行"剖析(Profiler)"工具来分析所选择的
应用。如下图所示: