title: How to reduce my flash game's background engine 600MB memory usage peaks?
body: <p>I'm creating a flash game in actionscript 3 with an infinite universe. Because the universe is infinite the background is created dynamically using the following background engine:</p>
  
  <p><em>BackgroundEngine.as</em></p>
  
  <pre><code>package com.tommedema.background
  {
      import br.com.stimuli.loading.BulkLoader;
  
      import com.tommedema.utils.Settings;
      import com.tommedema.utils.UtilLib;
  
      import flash.display.Bitmap;
      import flash.display.BitmapData;
      import flash.display.Sprite;
      import flash.events.Event;
      import flash.events.TimerEvent;
      import flash.utils.Timer;
  
      public final class BackgroundEngine extends Sprite
      {
          //general
          private static var isLoaded:Boolean = false;        
          private static var bulkLoader:BulkLoader = new BulkLoader("backgroundEngine");
          private static var assetsBitmapData:Array = [];
          private static var drawTimer:Timer;
  
          //objects
          private static var masterContainer:Sprite;
          private static var containers:Array = [];
  
          //stage
          private static var stageWidth:uint;
          private static var stageHeight:uint;
          private static var stageCenterX:Number;
          private static var stageCenterY:Number;
  
          //moves the background's X and Y coord
          public static function moveXY(xAmount:Number, yAmount:Number):void
          {
              if (!masterContainer) return;
              if (xAmount != 0) masterContainer.x += xAmount;
              if (yAmount != 0) masterContainer.y += yAmount;
          }
  
          //returns whether the background engine has been loaded already
          public static function loaded():Boolean
          {
              return isLoaded;
          }
  
          //loads the background engine
          public final function load():void
          {
              if (isLoaded) return;
              UtilLib.log("BackgroundEngine load.");
  
              //set stage width, height and center
              stageWidth = stage.stageWidth;
              stageHeight = stage.stageHeight;
              stageCenterX = stageWidth / 2;
              stageCenterY = stageHeight / 2;
  
              //load drawing timer
              drawTimer = new Timer(Settings.BG_DRAW_IV);
              drawTimer.addEventListener(TimerEvent.TIMER, updateBackground, false, 0, true);
              drawTimer.start();
  
              //retrieve background assets
              if ((bulkLoader.get("background/4.png")) &amp;&amp; (bulkLoader.get("background/4.png").isLoaded))
              {
                  loadAssets();
              }
              else
              {
                  bulkLoader.add(Settings.ASSETS_PRE_URL + "background/1.gif", {id: "background/1.gif"});
                  bulkLoader.add(Settings.ASSETS_PRE_URL + "background/2.png", {id: "background/2.png"});
                  bulkLoader.add(Settings.ASSETS_PRE_URL + "background/3.png", {id: "background/3.png"});
                  bulkLoader.add(Settings.ASSETS_PRE_URL + "background/4.png", {id: "background/4.png"});
                  bulkLoader.addEventListener(BulkLoader.COMPLETE, assetsComplete, false, 0, true);
                  bulkLoader.start();
              }
  
              //set isLoaded to true
              isLoaded = true;
          }
  
          //unloads the background engine
          public final function unload():void
          {
              if (!isLoaded) return;
              UtilLib.log("BackgroundEngine unload method has been called.");
  
              //unload drawTimer
              drawTimer.removeEventListener(TimerEvent.TIMER, updateBackground);
              drawTimer.stop();
              drawTimer = null;
  
              //clean the asset array
              assetsBitmapData = [];
  
              //remove containers
              for each (var container:Sprite in containers)
              {
                  if (container)
                  {
                      masterContainer.removeChild(container);
                      container = null;                   
                  }
              }
              containers = [];
  
              //remove master container
              if (masterContainer)
              {
                  removeChild(masterContainer);
                  masterContainer = null; 
              }
  
              //set isLoaded to false
              isLoaded = false;
          }
  
          //updates the background
          private final function updateBackground(event:TimerEvent):void
          {
              if (masterContainer)
              {
                  collectGarbage();
                  drawNextContainer();    
              }   
          }
  
          //poller function for drawing next background squares
          private static function drawNextContainer():void
          {
              var curContainer:Sprite = hasBackground(stageCenterX, stageCenterY);
              if (curContainer)
              {
                  if (!hasBackground(stageCenterX - stageWidth * 0.75, stageCenterY - stageHeight * 0.75)) //top left
                      drawNewSquare(curContainer.x - curContainer.width, curContainer.y - curContainer.height);
                  if (!hasBackground(stageCenterX, stageCenterY - stageHeight * 0.75)) //top
                      drawNewSquare(curContainer.x, curContainer.y - curContainer.height);
                  if (!hasBackground(stageCenterX + stageWidth * 0.75, stageCenterY - stageHeight * 0.75)) //top right
                      drawNewSquare(curContainer.x + curContainer.width, curContainer.y - curContainer.height);
                  if (!hasBackground(stageCenterX - stageWidth * 0.75, stageCenterY)) //center left
                      drawNewSquare(curContainer.x - curContainer.width, curContainer.y);
                  if (!hasBackground(stageCenterX + stageWidth * 0.75, stageCenterY)) //center right
                      drawNewSquare(curContainer.x + curContainer.width, curContainer.y);
                  if (!hasBackground(stageCenterX - stageWidth * 0.75, stageCenterY + stageHeight * 0.75)) //bottom left
                      drawNewSquare(curContainer.x - curContainer.width, curContainer.y + curContainer.height);
                  if (!hasBackground(stageCenterX, stageCenterY + stageHeight * 0.75)) //bottom center
                      drawNewSquare(curContainer.x, curContainer.y + curContainer.height);
                  if (!hasBackground(stageCenterX + stageWidth * 0.75, stageCenterY + stageHeight * 0.75)) //bottom right
                      drawNewSquare(curContainer.x + curContainer.width, curContainer.y + curContainer.height);
              }
          }
  
          //draws the next square and adds it to the master container
          private static function drawNewSquare(x:Number, y:Number):void
          {
              containers.push(genSquareBg());
              var cIndex:uint = containers.length - 1;
              containers[cIndex].x = x;
              containers[cIndex].y = y;
              masterContainer.addChild(containers[cIndex]);
          }
  
          //returns whether the given location has a background and if so returns the corresponding square
          private static function hasBackground(x:Number, y:Number):Sprite
          {
              var stageX:Number;
              var stageY:Number;
              for(var i:uint = 0; i &lt; containers.length; i++)
              {
                  stageX = masterContainer.x + containers[i].x;
                  stageY = masterContainer.y + containers[i].y;
                  if ((containers[i]) &amp;&amp; (stageX &lt; x) &amp;&amp; (stageX + containers[i].width &gt; x) &amp;&amp; (stageY &lt; y) &amp;&amp; (stageY + containers[i].height &gt; y)) return containers[i];
              }
              return null;
          }
  
          //polling function for old background squares garbage collection
          private static function collectGarbage():void
          {
              var stageX:Number;
              var stageY:Number;
              for(var i:uint = 0; i &lt; containers.length; i++)
              {
                  if ((containers[i]) &amp;&amp; (!isRequiredContainer(containers[i])))
                  {
                      masterContainer.removeChild(containers[i]);
                      containers.splice(i, 1);
                  }
              }
          }
  
          //returns whether the given container is required for display
          private static function isRequiredContainer(container:Sprite):Boolean
          {
              if (hasBackground(stageCenterX, stageCenterY) == container) //center
                  return true;
              if (hasBackground(stageCenterX - stageWidth * 0.75, stageCenterY - stageHeight * 0.75) == container) //top left
                  return true;
              if (hasBackground(stageCenterX, stageCenterY - stageHeight * 0.75) == container) //top
                  return true;
              if (hasBackground(stageCenterX + stageWidth * 0.75, stageCenterY - stageHeight * 0.75) == container) //top right
                  return true;
              if (hasBackground(stageCenterX - stageWidth * 0.75, stageCenterY) == container) //center left
                  return true;
              if (hasBackground(stageCenterX + stageWidth * 0.75, stageCenterY) == container) //center right
                  return true;
              if (hasBackground(stageCenterX - stageWidth * 0.75, stageCenterY + stageHeight * 0.75) == container) //bottom left
                  return true;
              if (hasBackground(stageCenterX, stageCenterY + stageHeight * 0.75) == container) //bottom center
                  return true;
              if (hasBackground(stageCenterX + stageWidth * 0.75, stageCenterY + stageHeight * 0.75) == container) //bottom right
                  return true;
              return false;
          }
  
          //dispatched when all assets have finished downloading
          private final function assetsComplete(event:Event):void
          {
              loadAssets();
          }
  
          //loads the assets
          private final function loadAssets():void
          {
              assetsBitmapData.push(bulkLoader.getBitmapData("background/1.gif")); //stars simple
              assetsBitmapData.push(bulkLoader.getBitmapData("background/2.png")); //star bright
              assetsBitmapData.push(bulkLoader.getBitmapData("background/3.png")); //cloud white
              assetsBitmapData.push(bulkLoader.getBitmapData("background/4.png")); //cloud red
              init();
          }
  
          //initializes startup background containers
          private final function init():void
          {
              masterContainer = new Sprite(); //create master container
  
              //generate default background container
              containers.push(genSquareBg()); //top left
              containers[0].x = 0;
              containers[0].y = 0;
              masterContainer.addChild(containers[0]);
  
              //display the master container
              masterContainer.x = -(stageWidth / 2);
              masterContainer.y = -(stageHeight / 2);
              masterContainer.cacheAsBitmap = true;
              addChild(masterContainer);
          }       
  
          //generates a background square
          private static function genSquareBg():Sprite
          {
              var width:Number = stageWidth * 2;
              var height:Number = stageHeight * 2;
              var startX:Number = 0;
              var startY:Number = 0;
  
              var scale:Number;
              var drawAmount:uint;
              var tmpBitmap:Bitmap;
              var i:uint;
  
              //create container
              var container:Sprite = new Sprite();
  
              //show simple stars background
              tmpBitmap = UtilLib.copyDataToBitmap(assetsBitmapData[0], false, 0x000000);
              tmpBitmap.x = startX;
              tmpBitmap.y = startY;
              container.addChild(tmpBitmap);
  
              //draw bright stars
              drawAmount = UtilLib.getRandomInt(1, 2);
              for(i = 1; i &lt;= drawAmount; i++)
              {
                  tmpBitmap = UtilLib.copyDataToBitmap(assetsBitmapData[1], true, 0x000000);
                  tmpBitmap.alpha = UtilLib.getRandomInt(3, 7) / 10;
                  tmpBitmap.rotation = UtilLib.getRandomInt(0, 360);
                  scale = UtilLib.getRandomInt(3, 10) / 10;
                  tmpBitmap.scaleX = scale; tmpBitmap.scaleY = scale;
                  tmpBitmap.x = UtilLib.getRandomInt(startX + tmpBitmap.width, width - tmpBitmap.width);
                  tmpBitmap.y = UtilLib.getRandomInt(startY + tmpBitmap.height, height - tmpBitmap.height);
                  container.addChild(tmpBitmap);
              }
  
              //draw white clouds
              drawAmount = UtilLib.getRandomInt(2, 4);
              for(i = 1; i &lt;= drawAmount; i++)
              {
                  tmpBitmap = UtilLib.copyDataToBitmap(assetsBitmapData[2], true, 0x000000);
                  tmpBitmap.alpha = UtilLib.getRandomInt(3, 10) / 10;
                  scale = UtilLib.getRandomInt(15, 40);
                  tmpBitmap.scaleX = scale / 10;
                  tmpBitmap.scaleY = UtilLib.getRandomInt(scale / 1.5, scale * 1.5) / 10;
                  tmpBitmap.x = UtilLib.getRandomInt(startX, width - tmpBitmap.width);
                  tmpBitmap.y = UtilLib.getRandomInt(startY, height - tmpBitmap.height);
                  container.addChild(tmpBitmap);
              }
  
              //draw red clouds
              drawAmount = UtilLib.getRandomInt(0, 2);
              for(i = 1; i &lt;= drawAmount; i++)
              {
                  tmpBitmap = UtilLib.copyDataToBitmap(assetsBitmapData[3], true, 0x000000);
                  tmpBitmap.alpha = UtilLib.getRandomInt(3, 10) / 10;
                  scale = UtilLib.getRandomInt(5, 40) / 10;
                  tmpBitmap.scaleX = scale; tmpBitmap.scaleY = scale;
                  tmpBitmap.x = UtilLib.getRandomInt(startX, width - tmpBitmap.width);
                  tmpBitmap.y = UtilLib.getRandomInt(startY, height - tmpBitmap.height);
                  container.addChild(tmpBitmap);
              }
  
              //convert all layers to a single bitmap layer and return
              return container;
          }
      }
  }
  </code></pre>
  
  <p><em>UtilLib.as copyDataToBitmap function:</em></p>
  
  <pre><code>//copies bitmap data and returns a new bitmap
          public static function copyDataToBitmap(bitmapData:BitmapData, transparency:Boolean = false, flatBackground:uint = 0x000000):Bitmap
          {
              var width:Number = bitmapData.width;
              var height:Number = bitmapData.height;
              var tmpBitmapData:BitmapData = new BitmapData(width, height, transparency, flatBackground);
              tmpBitmapData.copyPixels(bitmapData, new Rectangle(0, 0, width, height), new Point(0, 0));
              return new Bitmap(tmpBitmapData);
          }
  </code></pre>
  
  <p>The used background images are all around 30 kilobytes small, but some are very large:
  <a href="http://feedpostal.com/client/assets/background/1.gif" rel="nofollow noreferrer">alt text http://feedpostal.com/client/assets/background/1.gif</a>
  <a href="http://feedpostal.com/client/assets/background/2.png" rel="nofollow noreferrer">alt text http://feedpostal.com/client/assets/background/2.png</a>
  <a href="http://feedpostal.com/client/assets/background/3.png" rel="nofollow noreferrer">alt text http://feedpostal.com/client/assets/background/3.png</a>
  <a href="http://feedpostal.com/client/assets/background/4.png" rel="nofollow noreferrer">alt text http://feedpostal.com/client/assets/background/4.png</a></p>
  
  <p>I used to convert all container layers into 1 flat bitmap, but that seemed to reduce performance. The memory usage remained the same.</p>
  
  <p>The problem is that when I profile the application in Flex 3, the memory usage starts at 20MB (that's 1 container), when I start moving more containers are being loaded and while the old ones are being set to null the garbage collector isn't immediately removing them from memory causing a 600MB memory peak, after which it goes back to 20MB and starts again. Notice though that I have 8GB RAM with a 64bit OS, maybe flash is running the garbage collector on different intervals depending on your memory?</p>
  
  <p>If not, I would really appreciate some help on how to reduce the memory it uses. The images are already very optimized (used Photoshop).</p>