Let’s assume you know in advance how many renderers are needed. One way to solve the memory leak is to create a cache of renderers at startup.
1
2
3
4
5
6
7
|
private var cache:Array = new Array(); private function initRenderers():void { for (var i:int = 0; i < 200; i++) { renderers.push(new MyRenderer()); } } |
We can then modify our loadData method like this:
1
2
3
4
5
6
7
8
9
10
11
|
private function loadData():void { container.removeAllChildren(); var array:Array = getData(); for (var i:int = 0; i < array.length; i++) { var rend:MyRenderer = cache[i]; rend.data = array[i]; container.addChild(rend); i++; } } |
As you can see in the code we don’t create new renderers but we look for one in the cache. There are cases when you don’t know in advance which data are returned from the server and then you don’t know how many rendereres you need. In this case you need a dynamic cache.
Dynamic caching is based on an elastic mechanism. You have a place where you can look for a renderer: if there is one in the cache, that is returned, otherwise a new one is created temporarily. Better to see some code.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
public class MyRendererCache extends Object { private static var cache : ArrayCollection = new ArrayCollection(); public function MyRendererCache() { super(); for ( var x : Number = 0; x < 20; x ++ ) { cache.addItem( new MyRenderer() ); } } public static function getRenderer() : MyRenderer { var renderer : MyRenderer; if ( cache.length <= 0 ) { renderer = new MyRenderer(); } else { renderer = cache.removeItemAt( 0 ) as MyRenderer; } return renderer; } public static function setRenderer( renderer : MyRenderer ) : void { cache.addItem( renderer ); } } |
In the constructor you populate the cache with the minimu number of renderers, say 20 (lines 7-9). The cache has two static methods, getRenderer
and setRenderer
. The first is used to obtain a renderer, the second to give it back when done. If you look and lines 15-16 the cache returns a new renderer when the cache is empty. This way the number of renderers in memory can grow beyond the minimum number set in the constructor, but just temporarily, since the GC will delete them when not referenced anymore.
An important issue is related to the setRenderer
. When you don’t need a renderer anymore you have to return it back to the cache, otherwise we fall again in a memory leak as explained above. To achieve this we exploit the remove event of the renderer. The remove event is triggerer whenever a UI element is removed from the display list. For example when we call removeAllChildren()
such event is triggered for each renderer.
We can modify the renderer like this:
1
2
3
4
5
6
7
8
9
10
|
<mx:VBox xmlns:mx="http://www.adobe.com/2006/mxml" borderStyle="solid" width="200" remove="onRemove()" > private function onRemove():void { MyRendererCache.setRenderer(this); } .... </VBox> |
If you now run the profiler you will notice that memory grows until a given point and then keeps stable as in the figure below.
Congratulations! You have solved the memory leak!
Besides favoring the job of GC Adobe has allowed programmers to suggest the intervention of the GC. The command is in the flash.system
package and it is System.gc()
. By documentation this “Forces the garbage collection process” but in my experience it is just a vague suggestion of intervention. It can solve some situation, so it is worth trying it at the beginning, when you need a quick way to save some memory.
Full screen slides available here.
The source code of this post is available under the new BSD license.