title: Can't find huge memory leak in a Flex 4.6 web application
body: <p>I have <a href="http://apps.facebook.com/video-preferans/" rel="nofollow noreferrer">a Flex 4.6 web game</a> which displays 2 Lists with virtual layouts with 2 custom item renderers. The renderers consist mainly of  BitmapImages displaying user avatars + few Labels.</p>
  
  <p><img src="https://i.stack.imgur.com/O8R49.png" alt="enter image description here"></p>
  
  <p>The Lists are being updated often over TCP socket with gzipped JSON data. I <a href="https://stackoverflow.com/questions/8736769/merge-data-into-filtered-arraycollection-maybe-by-using-iviewcursor-or-localind">merge that data into 2 ArrayCollections</a> serving as dataProviders for the Lists. This seems to work well, the Lists do not flicker and are updated correctly (I've monitored debug traces a lot to get it right).</p>
  
  <pre><code>{
      lobby: [
          "OK252342810632",
          "OK122471020773",
          "DE54",
          "DE6577",
          "DE7981",
          "OK225312168135",
          "OK20629248715",
          "DE7880",
      ],
      games: [
          { 0: [] },
          { 9012: [
              "VK48058967",
              "MR14315189992643135976",
              "OK10218913103",
          ] },
          { 9013: [
              "OK305894249541",
              "OK151358069597",
              "OK515549948434",
          ] },
          { 9007: [
              "OK366541092321",
              "DE7062",
              "OK122700338897",
          ] },
          { 8993: [
              "OK78476527766",
              "VK5692120"
          ] }
      ]
  }
  </code></pre>
  
  <p>My problem is that the application gets sluggish very soon and for some users the plugin crashes and so the users complain a lot.</p>
  
  <p>In the profiles I see this picture (memory jumps to 20 MB and stays there or sometimes jumps to 40MB etc.):</p>
  
  <p><img src="https://i.stack.imgur.com/orUD2.png" alt="enter image description here"></p>
  
  <p>From profiler I can't figure out - what is leaking the memory. On the top I see <strong>Vector.&lt;*></strong> Class - whatever that means.</p>
  
  <p>I've tried so many things already to fix this issue:</p>
  
  <ul>
  <li><p>Switched from native JSON.parse() back to com.brokenfunction.json.decodeJson() (because I thought maybe Adobe got it wrong)</p>
  
  <ul>
  <li>I delete the JSON object after I've parsed it:</li>
  </ul>
  
  <p>private function handleTcpData(event:ProgressEvent):void {
      // read ByteArray over socket, unzip it - works well</p>
  
  <pre><code>var obj:Object = decodeJson(_bytes.toString());
  
  // merge into 2 ArrayCollections, update GUI... works well?
  
  for (var key:String in obj)
      delete obj[key];
  obj = null;
  </code></pre>
  
  <p>}</p>
  
  <ul>
  <li><p>I've switched ContentCache off - makes no difference</p></li>
  <li><p>I've stopped using any temp. Objects like <strong>{ id: "DE22" }</strong> and use the JSON Object directly </p></li>
  </ul></li>
  </ul>
  
  <p>Does anybody please have a good advice?</p>
  
  <p>I've read a lot of docs/blogs on AS3 garbage collection and the usual tips: set Object references to null, use weak event listeners, removeChild() does not free memory, etc. - but I don't see how to apply this to my problem.</p>
  
  <p>Below is my complete <strong>Lobby.mxml</strong> source code:</p>
  
  <pre><code>&lt;?xml version="1.0" encoding="utf-8"?&gt;
  &lt;s:Group 
      xmlns:fx="http://ns.adobe.com/mxml/2009" 
      xmlns:s="library://ns.adobe.com/flex/spark" 
      xmlns:mx="library://ns.adobe.com/flex/mx" 
      xmlns:comps="*"
      width="700" height="450"&gt;
  
      &lt;fx:Declarations&gt;
          &lt;s:RadioButtonGroup id="_filter" change="handleRadio(event);" /&gt;
      &lt;/fx:Declarations&gt;
  
      &lt;fx:Metadata&gt; 
          [Event(name="game", type="PrefEvent")] 
      &lt;/fx:Metadata&gt;
  
      &lt;fx:Script&gt;
          &lt;![CDATA[
              import mx.collections.ArrayCollection;
              import mx.utils.ObjectUtil;
  
              [Bindable]
              private var _games:ArrayCollection = new ArrayCollection();
              [Bindable]
              private var _users:ArrayCollection = new ArrayCollection();
  
              private function handleRadio(event:Event):void {
                  switch (_filter.selection) {
                      case _allBtn:
                          _games.filterFunction = null; 
                          break;
                      case _vacBtn:
                          _games.filterFunction = vacantGame;
                          break;
                      case _fullBtn:
                          _games.filterFunction = fullGame;
                          break;
                  }
                  _games.refresh();
              }
  
              private function vacantGame(obj:Object):Boolean {
                  for (var id:String in obj) {
                      var players:Array = obj[id];
                      return players.length &lt; 3;
                  }
  
                  return false;
              }
  
              private function fullGame(obj:Object):Boolean {
                  for (var id:String in obj) {
                      var players:Array = obj[id];
                      return players.length == 3;
                  }
  
                  return false;
              }
  
              // merge arrays of objects
              private function mergeObjs(ac:ArrayCollection, array:Array):void {
                  var i:int;
                  var j:int;
                  var n:int;
                  var src:Array = ac.source;
  
                  // 1) remove items missing in data from _data
                  FOUND1:
                  for (i = src.length - 1; i &gt;= 0; i--) {
                      for (j = array.length - 1; j &gt;= 0; j--) {
                          if (ObjectUtil.compare(src[i], array[j]) == 0)
                              continue FOUND1;
                      }
  
                      n = ac.getItemIndex(src[i]);
                      if (Util.DEBUG)
                          trace('REMOVED OBJ ' + src[i] + ' filtered n=' + n + ' i=' + i);
  
                      // remove visible items
                      if (n &gt; -1)
                          ac.removeItemAt(n);
                      // remove hidden (filtered) items
                      else
                          src.splice(i, 1);
                  }
  
  
                  // 3) add items appeared in data to _data
                  FOUND2:
                  for (j = 0; j &lt; array.length; j++) {
                      for (i = 0; i &lt; src.length; i++) {
                          if (ObjectUtil.compare(src[i], array[j]) == 0)
                              continue FOUND2;
                      }
  
                      if (Util.DEBUG) 
                          trace('ADDED OBJ ' + array[j] + ' i=' + i);
  
                      //ac.addItemAt(array[j], i);
                      ac.addItem(array[j]);
                  }
              }
  
              // merge arrays of strings (the user ids)
              private function mergeIds(ac:ArrayCollection, array:Array):void {
                  var i:int;
                  var j:int;
                  var n:int;
                  var src:Array = ac.source;
  
                  // 1) remove items missing in data from _data
                  FOUND1:
                  for (i = src.length - 1; i &gt;= 0; i--) {
                      for (j = array.length - 1; j &gt;= 0; j--) {
                          if (i == j)
                              continue FOUND1;
                      }
  
                      n = ac.getItemIndex(src[i]);
                      if (Util.DEBUG)
                          trace('REMOVED ID ' + src[i] + ' filtered n=' + n + ' i=' + i);
  
                      // remove visible items
                      if (n &gt; -1)
                          ac.removeItemAt(n);
                          // remove hidden (filtered) items
                      else
                          src.splice(i, 1);
                  }
  
  
                  // 3) add items appeared in data to _data
                  FOUND2:
                  for (j = 0; j &lt; array.length; j++) {
                      for (i = 0; i &lt; src.length; i++) {
                          if (i == j)
                              continue FOUND2;
                      }
  
                      if (Util.DEBUG) 
                          trace('ADDED ID ' + array[j] + ' i=' + i);
  
                      ac.addItem(array[j]);
                  }
              }
  
              public function update(games:Array, lobby:Array):void {
                  var vac:uint = 0;
                  var full:uint = 0;
  
                  for each (var game:Object in games) {
                      for (var id:String in game) {
                          var players:Array = game[id];
                          if (!players)
                              continue;
  
                          if (players.length &lt; 3)
                              vac++;
                          else if (players.length == 3)
                              full++;
                      }
                  }
  
                  if (games)
                      mergeObjs(_games, games);
  
                  if (lobby)
                      mergeIds(_users, lobby);    
  
                  _allBtn.label  = 'All ' + _games.source.length;
                  _vacBtn.label  = 'Vacant ' + vac;
                  _fullBtn.label = 'Full ' + full;
              }
  
              public function appendText(str:String):void {
                  _chat.appendText(str);
              }
          ]]&gt;
      &lt;/fx:Script&gt;
  
      &lt;s:VGroup width="100%" height="100%"&gt;
          &lt;s:HGroup width="100%" verticalAlign="baseline" paddingLeft="8" paddingRight="8"&gt;
              &lt;s:Label text="Игровые столы:" /&gt;
              &lt;s:RadioButton id="_allBtn" group="{_filter}" label="Все" selected="true" /&gt;
              &lt;s:RadioButton id="_vacBtn" group="{_filter}" label="Свободные" /&gt;
              &lt;s:RadioButton id="_fullBtn" group="{_filter}" label="Полные" /&gt;
              &lt;s:Spacer width="100%" /&gt;
              &lt;s:Label text="Игроки в лобби: {_users.length}" /&gt;
          &lt;/s:HGroup&gt;
  
          &lt;mx:HDividedBox width="100%" height="100%"&gt;
              &lt;s:List id="_gamesList" itemRenderer="Game" useVirtualLayout="true" dataProvider="{_games}" skinClass="PrefListSkin" width="100%" height="100%" minWidth="180"&gt;
                  &lt;s:layout&gt;
                      &lt;s:TileLayout /&gt;
                  &lt;/s:layout&gt;
              &lt;/s:List&gt;
  
              &lt;mx:VDividedBox width="180" height="100%" minWidth="180"&gt;
                  &lt;s:List id="_usersList" itemRenderer="User" useVirtualLayout="true" dataProvider="{_users}" skinClass="PrefListSkin" width="100%" height="100%" minHeight="150"&gt;
                      &lt;s:layout&gt;
                          &lt;s:TileLayout /&gt;
                      &lt;/s:layout&gt;
                  &lt;/s:List&gt;   
                  &lt;s:TextArea id="_chat"  width="100%" height="100%" minHeight="40" editable="false" fontSize="14" color="#000000" horizontalScrollPolicy="off"/&gt;
              &lt;/mx:VDividedBox&gt;
          &lt;/mx:HDividedBox&gt;
  
      &lt;/s:VGroup&gt;
  &lt;/s:Group&gt;
  </code></pre>
  
  <p><strong>UPDATE:</strong></p>
  
  <p>I must add, that my problem (users complain about reproducable Flash plugin crashes) has started after I have switched from XML to JSON.</p>
  
  <p>I have re-run the Profiler and <strong>why does it show 987 instances of the JSON.parseCore</strong> while there are only 400 users in the Lobby (and only few currently visible in the Lists with virtual layouts)?</p>
  
  <p><img src="https://i.stack.imgur.com/arl6K.png" alt="enter image description here"></p>
  
  <p>Any ideas please, what could have changed when I went from XML to JSON? (I didn't touch anyhting else). I was actually hoping for better performance, because I read about XML leaking memory in Flex sometimes...</p>