Flex内存优化专题之——3、开发中导致内存泄露的常见情况
通过上面的讨论我们可以知道,只要对象被其他活动对象(仍在运行的)所引用,那
么这个对象就不会被垃圾回收,从而可能造成内存泄露。
在我们的开发中,如下的一些情形会导致内存泄露:
(一) 被全局对象所引用的对象在它们不再使用时,开发者忘记从全局对象上清除对它
们的引用就会产生内存泄露。常见的全局对象有 stage,主 Application,类的静态成
员以及采用 singleton 模式创建的实例等。如果使用第三方框架,比如:
PureMvc,Cairongorm 等,要注意这些框架的实现原理,尤其要注意框架里面采用
singleton 模式创建的 controler 和 Model。
(二) 无限次触发的 Timer 会导致内存泄漏。无论无限次触发的 Timer 是否为全局对象,
无限次触发的 Timer 本身以及注册在 Timer 中的监听器对象都不会被垃圾回收。
请看下面的代码:
<?xml version="1.0" encoding="utf-8"?> <mx:Canvas xmlns:mx="http://www.adobe.com/2006/mxml" width="400" height="300" creationComplete="this.doIni()" backgroundColor="#8A85AC" borderColor="#F25488" cornerRadius="0" borderStyle="solid" borderThickness="3"> <mx:Script> <![CDATA[ import mx.events.ResizeEvent; [Bindable]private var timer:Timer=new Timer(1000); private var memoryBlocks:Array=new Array(); private function doIni():void { var mBlock:Array=this.allocateMemory(); memoryBlocks.push(mBlock); this.timer.addEventListener(TimerEvent.TIMER,onTimer); this.timer.start(); } private function onTimer(event:TimerEvent):void { trace(this.toString(); } private function allocateMemory():Array { var memoryBlock:Array=new Array(25600000); for(var i:uint=1;i<=25600000;i++) { memoryBlock[i-1]=i; } trace("allcate 100M memory!"); return memoryBlock; } ]]> </mx:Script> <mx:Label x="119" y="50" text="内存测试组件" width="139" color="#C14717" fontSize="19" fontWeight="bold"/> </mx:Canvas>
上面的代码自定义了一个测试内存泄露的 Canvas 组件,这个组件在初始化时开辟了
100M 内存(为了方便查看内存的回收情况),同时创建了一个每隔 1 秒钟,无限次数触发
的 Timer,并且启动了这个 Timer。
<?xml version="1.0" encoding="utf-8"?> <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" width="579"> <mx:Script> <![CDATA[ private var memoryTester:LargeMemoryCanvas=null; private function addMemoryTester():void { this.memoryTester=new LargeMemoryCanvas(); this.container.addChild(memoryTester); //this.stage.addEventListener(Event.RESIZE,this.memoryTester.doOnResize, false,0,true); } private function removeMemoryTester():void { this.container.removeChild(this.memoryTester); this.memoryTester=null; } private function gc():void { try { var lc1:LocalConnection = new LocalConnection(); var lc2:LocalConnection = new LocalConnection(); lc1.connect( "gcConnection" ); lc2.connect( "gcConnection" ); } catch (e:Error) { } } ]]> </mx:Script> <mx:VBox id="container" x="0" y="0" width="100%" height="100%"> <mx:Button label="强制回收内存" fontSize="12" width="130" click="this.gc();"/> <mx:Button label="创建内存消耗组件" fontSize="12" click="this.addMemoryTester();"/> <mx:Button label="移出内存消耗组件" fontSize="12" click="this.removeMemoryTester();"/> </mx:VBox> </mx:Application>
这是一个测试应用,该应用上有三个按钮,分别是“强制回收内存”,“创建内存
消耗组件”和“移出内存消耗组件”。点击“创建内存消耗组件”按钮就会执行
创建一个用于内存泄露测试的 Canvs 对象,并将其作为 container 的子对象显
示到界面上,点击“移出内存消耗组件”按钮则会将“创建内存消耗组件”按钮
所创建的 Canvs 对象从 container 的子对象列表中删除,并且永远不再使用。
应用运行后,我们先“创建内存消耗组件”按钮,然后再点击“移出内存消耗组
件”按钮,重复这样的操作,我们发现,由于Canvs对象上的无限次数触发的Timer
对象已经启动,导致 Canvs 对象所占用的内存无法被回收,内存会一直增加,最
终会导致浏览器崩溃。如果我们将 Canvs 初始化代码中启动 Timer 的语句注释
掉,重复上述的测试操作,内存会在某个时候减少,说明占用内存的 Canvs 对象
已经被垃圾回收。
通过上面那个简单的测试程序测试了 Timer 的情况,当然,将其稍加改造也
可以用来测试其他情况,我在这里所列举的情况内存泄露的情况都是利用上面那
个测试程序得到的结论。
(三) 通过隐式方式建立的对象之间的引用关系更容易被程序员所忽略,从而导致内存
泄露。 最常见的以隐式方式建立对象之间的引用就是“绑定”和“为对象添加事
件监听器”。通过测试我们发现“绑定”不会造成内存泄露,对象可以放心地绑定
全局对象。而调用 addEventListener()方法“为对象添加事件监听器”则可能产生内
存泄露,大多数内存泄露都因此而来:下面代码:
a.addEventListener(Event.EVENT_TYPE,b.listenerFunction)
使得 a 对象引用了 b 对象,如果 a 对象是一个全局对象(全局对象在应用运行期间
始终存在),则 b 对象永远不会被垃圾回收,可能会造成内存泄露。比如下面的代码就有
造成内存泄露的可能:
this.stage.addEventListener(Event.RESIZE,onResize);
上面代码中的 stage 是 UIComponent 的 stage 属性,表示当前 Flex 应用运行的“舞台”。
不过,通过以下三种方式使用 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);

浙公网安备 33010602011771号