Keep Your Flash Project's Memory Usage Stable With Object Pooling
Memory usage is an aspect of development that you really have to be careful about, or it might end up slowing down your app, taking up a lot of memory or even crashing everything. This tutorial will help you to avoid those bad potential outcomes!
Final Result Preview
Let's take a look at the final result we will be working towards:
Click anywhere on the stage to create a firework effect, and keep an eye on the memory profiler in the top-left corner.
Step 1: Introduction
If you have ever profiled your application using any profiling tool or used any code or library that tells you the current memory usage of your application, you may have noticed that many times the memory usage goes up, and then goes down again (if you haven't, your code is superb!). Well, although these spikes caused by big memory usage look kinda cool, it's not good news for either your application or (consequently) your users. Keep reading to understand why this happens and how to avoid it.
Step 2: Good and Bad Usage
The image below is a really great example of poor memory management. It's from a prototype of a game. You must notice two important things: the big spikes on memory usage and the memory usage peak. The peak is almost at 540Mb! That means this prototype alone reached the point of using 540Mb of the user's computer RAM - and that is something you definitely want to avoid.



This problem begins when you start creating a lot of object instances in your application. Unused instances will keep using your application's memory until the garbage collector runs, when they get deallocated - causing the big spikes. An even worse situation happens when the instances simply won't get deallocated, causing your application's memory usage to keep growing until something crashes or breaks. If you want to know more about the latter problem and how to avoid it, read this Quick Tip about garbage collection.
In this tutorial we will not address any garbage collector issues. We'll instead work on building structures that efficiently keep objects in the memory, making its usage completely stable and thus keeping the garbage collector from cleaning up memory, making the application faster. Take a look at the memory usage of the same prototype above, but this time optimized with the techniques shown here:



All this improvement can be achieved using object pooling. Read on to understand what it is and how it works.
Step 3: Types of Pools
Object pooling is a technique wherein a pre-defined number of objects are created when the application is initialized, and kept in the memory during the entire application lifetime. The object pool gives objects when the application requests them, and resets the objects back to the initial state when the application is finished using them. There are many types of object pools, but we will only take a look at two of them: the static and the dynamic object pools.
The static object pool creates a defined number of objects and only keeps that amount of objects during the entire application life time. If an object is requested but the pool has already given all its objects, the pool returns null. When using this kind of pool, it is necessary to address issues such as requesting an object and getting nothing back.
The dynamic object pool also creates a defined number of objects on initialization, but when an object is requested and the pool is empty, the pool creates another instance automatically and returns that object, increasing the pool size and adding the new object to it.
In this tutorial we will build a simple application that generates particles when the user clicks on the screen. These particles will have a finite lifetime, and then will be removed from the screen and returned to the pool. In order to do that, we will first create this application without object pooling and check the memory usage, and then implement the object pool and compare the memory usage to before.
Step 4: Initial Application
Open FlashDevelop (see this guide) and create a new AS3 project. We will use a simple small colored square as the particle image, which will be drawn with code and will move according to a random angle. Create a new class called Particle that extends Sprite. I'll assume that you can handle the creation of a particle, and just highlight the aspects that will keep track of the particle's lifetime and removal from screen. You can grab the full source code of this tutorial on the top of the page if you're having trouble creating the particle.
1 |
private var _lifeTime:int; |
2 |
|
3 |
public function update(timePassed:uint):void |
4 |
{
|
5 |
// Making the particle move
|
6 |
x += Math.cos(_angle) * _speed * timePassed / 1000; |
7 |
y += Math.sin(_angle) * _speed * timePassed / 1000; |
8 |
|
9 |
// Small easing to make movement look pretty
|
10 |
_speed -= 120 * timePassed / 1000; |
11 |
|
12 |
// Taking care of lifetime and removal
|
13 |
_lifeTime -= timePassed; |
14 |
|
15 |
if (_lifeTime <= 0) |
16 |
{
|
17 |
parent.removeChild(this); |
18 |
}
|
19 |
}
|
The code above is the code responsible for the particle's removal from the screen. We create a variable called _lifeTime
to contain the number of miliseconds that the particle will be on the screen. We initialize by default its value to 1000 on the constructor. The update()
function is called every frame and receives the amount of miliseconds that passed between frames, so that it can decrease the particle's lifetime value. When this value reaches 0 or less, the particle automatically asks its parent to remove it from the screen. The rest of the code takes care of the particle's movement.
Now we'll make a bunch of these be created when a mouse click is detected. Go to Main.as:
1 |
private var _oldTime:uint; |
2 |
private var _elapsed:uint; |
3 |
|
4 |
private function init(e:Event = null):void |
5 |
{
|
6 |
removeEventListener(Event.ADDED_TO_STAGE, init); |
7 |
// entry point
|
8 |
stage.addEventListener(MouseEvent.CLICK, createParticles); |
9 |
addEventListener(Event.ENTER_FRAME, updateParticles); |
10 |
|
11 |
_oldTime = getTimer(); |
12 |
}
|
13 |
|
14 |
private function updateParticles(e:Event):void |
15 |
{
|
16 |
_elapsed = getTimer() - _oldTime; |
17 |
_oldTime += _elapsed; |
18 |
|
19 |
for (var i:int = 0; i < numChildren; i++) |
20 |
{
|
21 |
if (getChildAt(i) is Particle) |
22 |
{
|
23 |
Particle(getChildAt(i)).update(_elapsed); |
24 |
}
|
25 |
}
|
26 |
}
|
27 |
|
28 |
private function createParticles(e:MouseEvent):void |
29 |
{
|
30 |
for (var i:int = 0; i < 10; i++) |
31 |
{
|
32 |
addChild(new Particle(stage.mouseX, stage.mouseY)); |
33 |
}
|
34 |
}
|
The code for updating the particles should be familiar to you: it's the roots of a simple time-based loop, commonly used in games. Don't forget the import statements:
1 |
import flash.events.Event; |
2 |
import flash.events.MouseEvent; |
3 |
import flash.utils.getTimer; |
You can now test your application and profile it using FlashDevelop's built-in profiler. Click a bunch of times on the screen. Here's what my memory usage looked like:



I clicked until the garbage collector started to run. The application created over 2000 particles that were collected. Is it starting to look like the memory usage of that prototype? It looks like it, and this is definitely not good. To make profiling easier, we'll add the utility that was mentioned in the first step. Here's the code to add in Main.as:
1 |
private function init(e:Event = null):void |
2 |
{
|
3 |
removeEventListener(Event.ADDED_TO_STAGE, init); |
4 |
// entry point
|
5 |
stage.addEventListener(MouseEvent.CLICK, createParticles); |
6 |
addEventListener(Event.ENTER_FRAME, updateParticles); |
7 |
|
8 |
addChild(new Stats()); |
9 |
|
10 |
_oldTime = getTimer(); |
11 |
}
|
Don't forget to import net.hires.debug.Stats
and it's ready to be used!
Step 5: Defining a Poolable Object
The application we built in Step 4 was pretty simple. It featured only a simple particle effect, but created a lot of trouble in the memory. In this step, we will begin working on an object pool in order to fix that problem.
Our first step towards a good solution is to think about how the objects can be pooled without problems. In an object pool, we need to always make sure that the object created is ready for use and that the object returned is completely "isolated" from the rest of the application (i.e. holds no references to other things). In order to force each pooled object to be able to do that, we are going to create an interface. This interface will define two important functions that the object must have: renew()
and destroy()
. That way, we can always call those methods without worrying about whether or not the object has them (because it will have). This also means that every object we want to pool will need to implement this interface. So here it is:
1 |
package
|
2 |
{
|
3 |
public interface IPoolable |
4 |
{
|
5 |
function get destroyed():Boolean; |
6 |
|
7 |
function renew():void; |
8 |
function destroy():void; |
9 |
}
|
10 |
}
|
Since our particles will be poolable, we need to make them implement IPoolable
. Basically we move all the code from their constructors to the renew()
function, and eliminate any external references to the object in the destroy()
function. Here's what it should look like:
1 |
/* INTERFACE IPoolable */
|
2 |
|
3 |
public function get destroyed():Boolean |
4 |
{
|
5 |
return _destroyed; |
6 |
}
|
7 |
|
8 |
public function renew():void |
9 |
{
|
10 |
if (!_destroyed) |
11 |
{
|
12 |
return; |
13 |
}
|
14 |
|
15 |
_destroyed = false; |
16 |
|
17 |
graphics.beginFill(uint(Math.random() * 0xFFFFFF), 0.5 + (Math.random() * 0.5)); |
18 |
graphics.drawRect( -1.5, -1.5, 3, 3); |
19 |
graphics.endFill(); |
20 |
|
21 |
_angle = Math.random() * Math.PI * 2; |
22 |
|
23 |
_speed = 150; // Pixels per second |
24 |
|
25 |
_lifeTime = 1000; // Miliseconds |
26 |
}
|
27 |
|
28 |
public function destroy():void |
29 |
{
|
30 |
if (_destroyed) |
31 |
{
|
32 |
return; |
33 |
}
|
34 |
|
35 |
_destroyed = true; |
36 |
|
37 |
graphics.clear(); |
38 |
}
|
The constructor also shouldn't require any arguments any more. If you want to pass any information to the object, you'll have to do it through functions now. Due to the way that the renew()
function works now, we also need to set _destroyed
to true
in the constructor so that the function can be run.
With that, we have just adapted our Particle
class to behave as an IPoolable
. That way, the object pool will be able to create a pool of particles.
Step 6: Starting the Object Pool
It is time now to create a flexible object pool which can pool any object we want. This pool will act a bit like a factory: instead of using the new
keyword to create objects you can use, we will instead call a method in the pool that returns to us an object.
For the purposes of simplicity, the object pool will be a Singleton. That way we can access it anywhere within our code. Start by creating a new class called "ObjectPool" and adding the code to make it a Singleton:
1 |
package
|
2 |
{
|
3 |
public class ObjectPool |
4 |
{
|
5 |
private static var _instance:ObjectPool; |
6 |
private static var _allowInstantiation:Boolean; |
7 |
|
8 |
public static function get instance():ObjectPool |
9 |
{
|
10 |
if (!_instance) |
11 |
{
|
12 |
_allowInstantiation = true; |
13 |
_instance = new ObjectPool(); |
14 |
_allowInstantiation = false; |
15 |
}
|
16 |
|
17 |
return _instance; |
18 |
}
|
19 |
|
20 |
public function ObjectPool() |
21 |
{
|
22 |
if (!_allowInstantiation) |
23 |
{
|
24 |
throw new Error("Trying to instantiate a Singleton!"); |
25 |
}
|
26 |
}
|
27 |
|
28 |
}
|
29 |
|
30 |
}
|
The variable _allowInstantiation
is the core of this Singleton implementation: it's private, so only the own class can modify, and the only place where it should be modified is before creating the first instance of it.
We must now decide how to hold the pools inside this class. Since it will be global (i.e. can pool any object in your application), we need to first come up with a way to always have an unique name for each pool. How to do that? There are many ways, but the best I've found so far is to use the objects' own class names as the pool name. That way, we could have a "Particle" pool, a "Enemy" pool and so on... but there's another problem. Class names only have to be unique within their packages, so for instance a class "BaseObject" within the "enemies" package and a class "BaseObject" within the "structures" package would be allowed. That would cause problems in the pool.
The idea of using class names as identifiers for the pools is still great, and this is where flash.utils.getQualifiedClassName()
comes to help us. Basically this function generates a string with the full class name, including any packages. So now, we can use each object's qualified class name as the identifier for their respective pools! This is what we will add in the next step.
Step 7: Creating Pools
Now that we have a way to identify pools, it's time to add the code that creates them. Our object pool should be flexible enough to support both static and dynamic pools (we talked about these in Step 3, remember?). We also need to be able to store the size of each pool and how many active objects there are in each one. A good solution for that is to create a private class with all this information and store all pools within an Object
:
1 |
package
|
2 |
{
|
3 |
public class ObjectPool |
4 |
{
|
5 |
private static var _instance:ObjectPool; |
6 |
private static var _allowInstantiation:Boolean; |
7 |
|
8 |
private var _pools:Object; |
9 |
|
10 |
public static function get instance():ObjectPool |
11 |
{
|
12 |
if (!_instance) |
13 |
{
|
14 |
_allowInstantiation = true; |
15 |
_instance = new ObjectPool(); |
16 |
_allowInstantiation = false; |
17 |
}
|
18 |
|
19 |
return _instance; |
20 |
}
|
21 |
|
22 |
public function ObjectPool() |
23 |
{
|
24 |
if (!_allowInstantiation) |
25 |
{
|
26 |
throw new Error("Trying to instantiate a Singleton!"); |
27 |
}
|
28 |
|
29 |
_pools = {}; |
30 |
}
|
31 |
|
32 |
}
|
33 |
|
34 |
}
|
35 |
|
36 |
class PoolInfo |
37 |
{
|
38 |
public var items:Vector.<IPoolable>; |
39 |
public var itemClass:Class; |
40 |
public var size:uint; |
41 |
public var active:uint; |
42 |
public var isDynamic:Boolean; |
43 |
|
44 |
public function PoolInfo(itemClass:Class, size:uint, isDynamic:Boolean = true) |
45 |
{
|
46 |
this.itemClass = itemClass; |
47 |
items = new Vector.<IPoolable>(size, !isDynamic); |
48 |
this.size = size; |
49 |
this.isDynamic = isDynamic; |
50 |
active = 0; |
51 |
|
52 |
initialize(); |
53 |
}
|
54 |
|
55 |
private function initialize():void |
56 |
{
|
57 |
for (var i:int = 0; i < size; i++) |
58 |
{
|
59 |
items[i] = new itemClass(); |
60 |
}
|
61 |
}
|
62 |
}
|
The code above creates the private class which will contain all the information about a pool. We also created the _pools
object to hold all object pools. Below we will create the function that registers a pool in the class:
1 |
public function registerPool(objectClass:Class, size:uint = 1, isDynamic:Boolean = true):void |
2 |
{
|
3 |
if (!(describeType(objectClass).factory.implementsInterface.(@type == "IPoolable").length() > 0)) |
4 |
{
|
5 |
throw new Error("Can't pool something that doesn't implement IPoolable!"); |
6 |
return; |
7 |
}
|
8 |
|
9 |
var qualifiedName:String = getQualifiedClassName(objectClass); |
10 |
|
11 |
if (!_pools[qualifiedName]) |
12 |
{
|
13 |
_pools[qualifiedName] = new PoolInfo(objectClass, size, isDynamic); |
14 |
}
|
15 |
}
|
This code looks a bit trickier, but don't panic. It's all explained here. The first if
statement looks really weird. You may have never seen those functions before, so here's what it does:
- The describeType() function creates a XML containing all the information about the object we passed it.
- In the case of a class, everything about it is contained within the
factory
tag. - Inside that, the XML describes all the interfaces that the class implements with the
implementsInterface
tag. - We do a quick search to see if the
IPoolable
interface is among them. If so, then we know we can add that class to the pool, because we will be able to successfully cast it as anIObject
.
The code after this check just creates an entry within _pools
if one didn't already exist. After that, the PoolInfo
constructor calls the initialize()
function within that class, effectively creating the pool with the size we want. It's now ready to be used!
Step 8: Getting an Object
In the last step we were able to create the function that registers an object pool, but now we need to get an object in order to use it. It's very straightforward: we get an object if the pool isn't empty and return it. If the pool is empty, we check whether it's dynamic; if so, we increase its size, and then create a new object and return it. If not, we return null. (You can also choose to throw an error, but it's better to just return null and make your code work around this situation when it happens.)
Here's the getObj()
function:
1 |
public function getObj(objectClass:Class):IPoolable |
2 |
{
|
3 |
var qualifiedName:String = getQualifiedClassName(objectClass); |
4 |
|
5 |
if (!_pools[qualifiedName]) |
6 |
{
|
7 |
throw new Error("Can't get an object from a pool that hasn't been registered!"); |
8 |
return; |
9 |
}
|
10 |
|
11 |
var returnObj:IPoolable; |
12 |
|
13 |
if (PoolInfo(_pools[qualifiedName]).active == PoolInfo(_pools[qualifiedName]).size) |
14 |
{
|
15 |
if (PoolInfo(_pools[qualifiedName]).isDynamic) |
16 |
{
|
17 |
returnObj = new objectClass(); |
18 |
|
19 |
PoolInfo(_pools[qualifiedName]).size++; |
20 |
PoolInfo(_pools[qualifiedName]).items.push(returnObj); |
21 |
}
|
22 |
else
|
23 |
{
|
24 |
return null; |
25 |
}
|
26 |
}
|
27 |
else
|
28 |
{
|
29 |
returnObj = PoolInfo(_pools[qualifiedName]).items[PoolInfo(_pools[qualifiedName]).active]; |
30 |
|
31 |
returnObj.renew(); |
32 |
}
|
33 |
|
34 |
PoolInfo(_pools[qualifiedName]).active++; |
35 |
|
36 |
return returnObj; |
37 |
}
|
In the function, first we check that the pool actually exists. Assuming that condition is met, we check whether the pool is empty: if it is but it is dynamic, we create a new object and add into the pool. If the pool isn't dynamic, we stop the code there and just return null. If the pool still has an object, we get the object closest to the beginning of the pool and call renew()
on it. This is important: the reason we call renew()
on an object that was already in the pool is to guarantee that this object will be given at a "usable" state.
You are probably wondering: why don't you also use that cool check with describeType()
in this function? Well, the answer is simple: describeType()
creates an XML every time we call it, so it's very important to avoid the creation of objects that use a lot of memory and that we can't control. Besides, only checking to see whether the pool really exists is enough: if the class passed doesn't implement IPoolable
, that means we wouldn't even be able to create a pool for it. If there isn't a pool for it, then we definitely catch this case in our if
statement at the beginning of the function.
We can now modify our Main
class and use the object pool! Check it out:
1 |
private function init(e:Event = null):void |
2 |
{
|
3 |
removeEventListener(Event.ADDED_TO_STAGE, init); |
4 |
// entry point
|
5 |
stage.addEventListener(MouseEvent.CLICK, createParticles); |
6 |
addEventListener(Event.ENTER_FRAME, updateParticles); |
7 |
|
8 |
_oldTime = getTimer(); |
9 |
|
10 |
ObjectPool.instance.registerPool(Particle, 200, true); |
11 |
}
|
12 |
|
13 |
private function createParticles(e:MouseEvent):void |
14 |
{
|
15 |
var tempParticle:Particle; |
16 |
|
17 |
for (var i:int = 0; i < 10; i++) |
18 |
{
|
19 |
tempParticle = ObjectPool.instance.getObj(Particle) as Particle; |
20 |
tempParticle.x = e.stageX; |
21 |
tempParticle.y = e.stageY; |
22 |
|
23 |
addChild(tempParticle); |
24 |
}
|
25 |
}
|
Hit compile and profile the memory usage! Here's what I got:



That's kinda cool, isn't it?
Step 9: Returning Objects to the Pool
We have successfully implemented an object pool that gives us objects. That's amazing! But it isn't over yet. We're still only getting objects, but never returning them when we don't need them any more. Time to add a function to return objects inside ObjectPool.as
:
1 |
public function returnObj(obj:IPoolable):void |
2 |
{
|
3 |
var qualifiedName:String = getQualifiedClassName(obj); |
4 |
|
5 |
if (!_pools[qualifiedName]) |
6 |
{
|
7 |
throw new Error("Can't return an object from a pool that hasn't been registered!"); |
8 |
return; |
9 |
}
|
10 |
|
11 |
var objIndex:int = PoolInfo(_pools[qualifiedName]).items.indexOf(obj); |
12 |
|
13 |
if (objIndex >= 0) |
14 |
{
|
15 |
if (!PoolInfo(_pools[qualifiedName]).isDynamic) |
16 |
{
|
17 |
PoolInfo(_pools[qualifiedName]).items.fixed = false; |
18 |
}
|
19 |
|
20 |
PoolInfo(_pools[qualifiedName]).items.splice(objIndex, 1); |
21 |
|
22 |
obj.destroy(); |
23 |
|
24 |
PoolInfo(_pools[qualifiedName]).items.push(obj); |
25 |
|
26 |
if (!PoolInfo(_pools[qualifiedName]).isDynamic) |
27 |
{
|
28 |
PoolInfo(_pools[qualifiedName]).items.fixed = true; |
29 |
}
|
30 |
|
31 |
PoolInfo(_pools[qualifiedName]).active--; |
32 |
}
|
33 |
}
|
Let's go through the function: the first thing is to check whether there's a pool of the object that was passed. You're used to that code - the only difference is that now we're using an object instead of a class to get the qualified name, but that doesn't change the output).
Next, we get the index of the item in the pool. If it's not in the pool, we just ignore it. Once we verify that the object is in the pool, we must break the pool at where the object is currently at and reinsert the object at the end of it. And why? Because we're counting the used objects from the beginning of the pool, we need to reorganize the pool to make all returned and unused objects to be at the end of it. And that's what we do in this function.
For static object pools, we create a Vector
object that has fixed length. Due to that, we can't splice()
it and push()
objects back. The workaround to this is to change the fixed
property of those Vector
s to false
, remove the object and add it back at the end, and then change the property back to true
. We also need to decrease the number of active objects. After that, we're done returning the object.
Now that we have created the code to return an object, we can make our particles return themselves to the pool once they reach the end of their lifetimes. Inside Particle.as
:
1 |
public function update(timePassed:uint):void |
2 |
{
|
3 |
// Making the particle move
|
4 |
x += Math.cos(_angle) * _speed * timePassed / 1000; |
5 |
y += Math.sin(_angle) * _speed * timePassed / 1000; |
6 |
|
7 |
// Small easing to make movement look pretty
|
8 |
_speed -= 120 * timePassed / 1000; |
9 |
|
10 |
// Taking care of lifetime and removal
|
11 |
_lifeTime -= timePassed; |
12 |
|
13 |
if (_lifeTime <= 0) |
14 |
{
|
15 |
parent.removeChild(this); |
16 |
|
17 |
ObjectPool.instance.returnObj(this); |
18 |
}
|
19 |
}
|
Notice that we added a call to ObjectPool.instance.returnObj()
in there. That's what makes the object return itself to the pool. We can now test and profile our app:



And there we go! Stable memory even when hundreds of clicks were made!
Conclusion
You now know how to create and use an object pool to keep your app's memory usage stable. The class we built built can be used anywhere and it's really simple to adapt your code to it: at the beginning of your app, create object pools for every kind of object you want to pool, and whenever there is a new
keyword (meaning the creation of an instance), replace it with a call to the function that gets an object for you. Don't forget to implement the methods that the interface IPoolable
requires!
Keeping your memory usage stable is really important. It saves you a lot of trouble later in your project when everything starts to fall apart with unrecycled instances still responding to event listeners, objects filling up the memory you have available to use and with the garbage collector running and slowing everything down. A good recommendation is to always use object pooling from now on, and you'll notice your life will be much easier.
Also notice that although this tutorial was aimed for Flash, the concepts developed in it are global: you can use it on AIR apps, mobile apps and anywhere it fits. Thanks for reading!