Sunday 29 April 2012

The Ultimate Answer to Titanium memory leaks




The question


What are memory leaks and should Titanium programmer worry about memory leaks?


The answer to that question is as usual - it depends.


Memory leaks are situations when allocated memory in applications grows in uncontrolled way and never gets released. In some situations that can lead to application slowdown and crash.


Memory allocation and deallocation in JavaScript environments is managed automatically by garbage collection mechanism and programmers, in theory, should not worry about memory. In practice, there are situations where programmer should worry about memory handling.

JavaScript by itself has no memory problems, it's always interaction of native and host objects, or host objects themselves, that cause memory leaks. Titanium, as JavaScript host environment, is not different, but it has its own specifics.


One thing that can cause memory leaks in Titanium are application-level event listeners: Ti.App.addEventListener, "location", etc. That kind of listeners remain in memory until application runs (or until they are removed) and in situations where they are being added multiple times (by programer's error), the application will eventually crash.
Sometimes programmers use app-level events for communication between different UI components and in that cases memory leaks can happen because components will be "trapped" in app-level event. They are not like component-level events that are removed when component is removed from memory (usually, when window closes).


The problem


There are more situations that can potentially cause memory leaks. No one is sure in what situations cause memory leaks in Titanium (situations vary and are very specific) but it seems that similar things from other JavaScript environments apply in Titanium also (closures, listeners, circular references). In some cases it's Titanium's fault, but sometimes it is just lack of understanding.


Let's say that "app.js" contains this:

function createViews() {
    var views = [];
     for (var i = 0; i < 100; ++i) {
         views.push(Ti.UI.createView());
    } 
}
createViews();

Should views be freed from memory?

There was actually a bug filled with this kind of situation as memory leak situation. The problem here was not in Titanium, but in understanding (and Android emulator :) ). Some people suggested that "views" variable should be set to "null". Some suggested that all items in "views" array should be set to "null". But nothing is necessary here (and it shouldn't be) because "views" array is local variable in scope created by "createViews" function that will be destroyed after that function finishes with execution. Setting a variable to "null" in this case has no sense (as it has been shown in that bug report).

Setting a variable to "null" comes from web development, where in some cases (in older versions of IE primarily) interaction between host and native objects caused memory leaks and setting a variable to "null" would solve memory issues. But we should not be blind and use that technique everywhere without understanding the problem because it is not guarantied that setting a variable to "null" will solve memory issue or that setting variable to "null" is really necessary. We, as web developers, must deal with memory leaks in older versions of IE just because it is still used by many people. As Titanium developers we are in much better situation and we can report memory leak issue and wait until it's fixed (usually memory leak issues are fixed soon) and release our application without ugly workarounds.



The solution


Understanding.


The key of finding memory leak situations and solution of that problem is understanding. Titanium environment is one implementation of ECMAScript standard and therefore it should obey to the rules defined by that specification. I'll try to explain some basic concepts.

Scope is part of the program in which variables are bound to the values (variable is always declared with "var" keyword). We usually say that variable references value, but it would be better to say that variable identifies value (to not mix terminology with references from C-like programming languages). So, variables are not true references and we don't deal with low level things, but rather with higher level abstraction.


There is one global scope and new scopes are created by functions. When new function is defined, a reference to parent scope is stored (only global scope has no parent scope). That's why scopes are static (lexical), but variables and their values are resolved dynamically:

//app.js
//global scope 
this; //global object
this.Ti; //Titanium object

//cool so far :)
//let's declare a variable

var obj = { prop: 'abc' }; //created new object with string property
this.obj === obj; //true, variables defined in global scope are properties of global object
hm = 42;
this.hm === hm; //true, when no "var" is used, 
//property of global object is accessed if value is set, 
//reference exception is thrown otherwise

var oneMoreObj = obj; //created one more identifier to the object

function createNewScope(destroy) {
    destroy = null;
    var something = 2; 
    Ti.API.info(obj.prop);
}
createNewScope(obj); // abc

obj.prop = 'cba';

createNewScope(oneMoreObj); //cba


What happened here?

As I said, functions upon creation store a reference to the scope in which they are created. When they are executed, they create list of their own local variables, inner functions and formal parameters (we say that variables are "hoisted" to the top of the scope). When some variable is accessed during execution, it is resolved by lookup in list that current scope contains and if it is not found, it's searched in similar way in parent scopes (until global scope is reached). That's how "obj" variable is resolved inside "createNewScope" - variable is not found in current scope and it is searched in parent scope in which it's defined.
Note that "destroy" variable does not actually destroy outside object (although, modification would change it) because "destroy" is just one more identifier and setting it to "null" is just identifies different value (it doesn't change what "obj" identifies).
When function finishes with execution, local variables ("something" in this case) are destroyed in most cases. But there are cases when function has finished with execution, but scope and its variables are not destroyed. Functions are first-class citizens and they can be stored in variables, returned from function, etc. Because of that and the fact that scopes are static (they contain a reference to scope in which they are defined), they are not closed when they are finished with execution in cases when some child scope uses variables from parent scope (it forms so called closure). For example:


function createFunc() {
    var test = "Closure"; 
    return function() { alert(test); } 
}

var func = createFunc();
//createFunc has finished with execution, but we can still get "test" variable
func();


Sometimes that can lead to strange effects.


for (var i = 0; i < buttons.length; ++i) {
    buttons[i].addEventListener('click', function() {
        alert(i);
    });
}

This use-case usually produces WTF-moment because click of any button alerts same value. What happened here ... "buttons" (as identifiers of host objects) will hold event listener function. That function will contain a reference to parent scope. The point here is that all listener functions will contain a reference to same parent scope and that at the time button was actually clicked, "for" loop has finished with execution (and "i" variable will have same value it had at the end of the loop). To avoid that, closure can be created that captures value of "i" variable in each loop:


for (var i = 0; i < buttons.length; ++i) {
    buttons[i].addEventListener('click',
        (function(currentI) {
            return function() {
                alert(currentI);
            }; 
         })(i)
    );
}


The trick here is creation of self-invoking function. Functions can be anonymous and they can be invoked upon creation. To create anonymous function:


function() {}


To invoke it:


(function() {})()


Note that value of "i" is formal parameter to self-invoking function and because of created closure, parent scope of event listener function (scope created by anonymous function) won't close itself until button is removed from memory (along with reference to listener function).
In Titanium, button should be removed from memory when window closes and when nothing else identifies it from JS. Current documentation, in my opinion, does not explain that precise enough.


win.remove(view);  // view & button still exist
view = null; // deletes the view and its proxy, but not the button!


To be precise, "win.remove" should remove view's visual representation on the screen. When it is not present on the screen and nothing identifies it from JS, it should be removed from memory (otherwise, it's memory leak situation by definition). Setting variable to "null" does not delete the view directly (as it may sound from the comment from docs), the point here is that "view" variable now identifies to different value and Titanium host object "TiUIView" is not identified from JS anymore and therefore should be marked for garbage collection sometimes in the future. We could also set "view" to 42 (or anything else), it would not make a difference. If "view" variable is created in scope that is not global, it should be marked for garbage collection when that scope closes (remember bug report above). That's why, the best is to avoid using global scope except for function definition and some other controlled use cases.

So why to set a variable to "null" (or 42 :) ). In some cases (closures, circular references), when garbage collection mechanism misses to detect objects that it needs to deallocate, that can help. But it is not guaranteed that will help. As I said, it's important to understand the problem so it can be solved. Hope this article helps that cause. :)

42 comments:

  1. Very nice article Ivan. Thanks for taking the time to point out the "set objects to null" crazyness ;-)

    ReplyDelete
  2. Good entry, Ivan. Thanks for sharing! But... now I have a new doubt, what happens when using Ti.App.addEventListener inside a commonJS module? Is it automatically removed when the module instance is destroyed? Even more, in the past (previous to commonJS adoption) using Ti.App events used to create serious memory leaks, since all vars used inside were converted to global scope. That made me create a special javascript function to capture that events and then call global objects to manage the events. Since I'm using commonJS I've unconcerned about the problem but, is still there?

    ReplyDelete
    Replies
    1. There is no difference regarding app-level listeners because UI components gets trapped in app-level listener, not CommonJS modules. CommonJS module is just one JS context (it has its own global scope) that lives inside app. You still need to create UI components in that JS context and by using app-level events you will be facing same problems.

      People usually use app-level events because component-based events are not very flexible so app-level events seems like magical solution. But, with good approach, components can communicate with each other by using component-based events and it that case there is no worry about memory handling. :)

      Delete
  3. Ivan, thanks for the article. I learned a lot, but now I also know that I have more to learn. Would you be able to give examples of both app-level events and component-based events? I'm not sure I understand the difference. Thanks

    ReplyDelete
    Replies
    1. Hello Casey. App-level events are events created by "Ti.App.addEventListener", while component-based are events created by component's "addEventListner". For example:

      var button = Ti.UI.createButton();

      button.addEventListener("click", function() {}); //component-based listener

      Ti.App.addEventListener("do_something", function() { button.backgroundColor = "#f00"; }); //app-level event


      The difference is that component-based events are automatically destroyed when component is destroyed (usually, when window closes and nothing identifies it from JS), while app-level events remain in memory until app-runs. Because of that, in above example, "button" will remain in memory even when window closes and it that process is repeated, app will eventually crash.

      Delete
  4. Ok! Thank you for the reply Ivan. Much appreciated. And I'm happy to report that all of my event listeners are component based.

    But I do have one more question, if you don't mind. You said that "component-based events are automatically destroyed when component is destroyed (usually, when window closes...)"

    My first app that I have created used tabs. In the app.js file I create the tabs and I also create a window for each tab. Each time I create a window I give it a URL, so each window (and each tab) is handled in it's own js file. Those windows never actually close. The window that is shown depends on the tab that is clicked. Within each window I create views, and in the views I put UI elements like buttons, tableviews, etc.

    So, given this architecture, am I prone to memory leaks? Are my buttons, etc staying in memory and causing problems? And if so, how should I adjust so that each window is actually closed?

    Thank you again, Ivan, for your help.

    ReplyDelete
    Replies
    1. No problem, feel free to ask. :)

      I would recommend that you don't use windows with "url". They have been known as buggy. I highly recommend usage of CommonJS. Also, as I see in your Q&A question, you're using global scope. As I said in this article, avoid using global scope, except for function declarations and some other cases. If nothing else, create self-invoking function and wrap your code in it.

      Window will be closed when tabgroup closes. Tabgroup, tab and window are context type components and they are important from memory handling point of view. When context closes, components from that context should be freed from memory if nothing else references it from JS.

      Delete
  5. (I gave a more detailed description of how my app is setup here:

    http://developer.appcelerator.com/question/136196/not-polluting-the-global-name-space-while-also-preventing-memory-leaks

    ReplyDelete
  6. Thanks a lot for this article, Ivan. I helped me understand the problem rather than just trying to fix it blindly. Knowledge is power I guess. I do have one question for you if you don't mind.

    I have an app using a NavigationGroup (specifically an iPhoneNavigationGroup), in order to have organized window hierarchy. Upon pressing the "back" button, the close() event is called in the current Window object. If that window has elements in it - like a button or some labels - should I be taking care to set those objects' variables to null to prepare them for garbage collection, perhaps using the Window.addEventListener to look for the "close" event? Or is there a better way to do that?

    Thanks again for the illuminating article.

    ReplyDelete
    Replies
    1. Hello Brendan.


      Last week I was experimenting with NavigationGroup component and I had no memory leaks despite the fact that I didn't set any variable to "null". So, setting to "null" is not requirement and if you have to set to "null", then something is probably wrong. Just avoid global scope and app-level listeners and you should be fine. When window/tabgroup closes, components they contain will be marked for GC (if nothing from JS reference them).

      Delete
  7. Ivan,

    App-level events are not a source of memory leaks on their own. It's what you do inside such events that can cause a leak. Because the app-level event must remain in scope until the app closes or listener is removed, any variables/objects it refers to also must remain in scope. It's those references that cause leaks. App-level events are a valid technique. You just need to take care to remove them when they're no longer needed and use care when referring to local variables. One pattern than can work is to use the app-level event as a "router" to fire off component level events (listeners in your windows/views/etc.) That way, if the component goes out of scope, the app-level event doesn't maintain a reference to the component objects. It just fires an event and no one is there to listen for it.

    As for setting objects to null, that is a useful technique for releasing memory held by global variables. Those aren't cleared automatically like function-scope variables. Of course, the variable still exists -- it's value is just set equal to "null". However, when a JavaScript object holding a Titanium proxy (view, button, window, etc.) is set to null, the Kroll bridge can signal to the underlying operating system to release the associate memory at the native level.

    Tim

    ReplyDelete
    Replies
    1. Hello Tim, thanks for the comment and detailed explanation.


      Please note that I didn't say that app-level events cause memory leaks, but that they can cause them (in many cases, they are the cause of memory leaks).

      In general, I am not against app-level events, there are cases when they must be used. But I am against their usage in situations when their usage is not necessary. Using app-level events for communication between UI components is bad practice for few reasons: dev needs to take care about removing listeners when window closes, which is more error-prone and more slower. It's easy to take care about few listeners, but when there are few dozens of them ...

      I would like to see that "routing" technique, could you post some small example? I can't understand how can component go out of scope (if it is not removed manually).



      As for setting global variables to "null" ... the point of this article was to educate people not to do that. That should also be considered as bad practice. Devs usually do this:

      //global scope
      var win = Ti.UI.createWindow();

      win.addEventListener('close', function() {
      win = null;
      });

      win.open();

      But, with just small modification, adding "close" event listener and setting "win" to "null" is not necessary:

      (function() {
      var win = Ti.UI.createWindow();
      win.open();
      })();


      That solution is cleaner, faster and there is no chance of making error (like to forget to set some variable to "null").


      The best solution is to not create the problem in the first place. :)


      The problem with setting variables to "null" is that devs started to use that practice everywhere without truly understanding the problem (many of them identified nullifying with memory deallocation, which is totally wrong).



      At the end, when someone knows what he is doing, global variables and events can be useful. But many devs (and especially newcomers) do not know what are they doing. Because of that, we should promote good coding practices. Global variables, events (when they are not really necessary) and nullifying variables without valid reason, IMHO, is not.

      This practice that I use and promote, I use on large and complex project and I don't handle memory by myself and have no memory issues.

      Delete
  8. Hey, thanks for that great article.
    One question. Why ist "adding "close" event listener and setting "win" to "null" is not necessary:"
    in your last example?

    ReplyDelete
    Replies
    1. Thanks.


      It's not necessary because variables are local variables in function scope created by self-invoking function. That scope will be closed once that function finishes with execution (in this particular case).

      If there were listeners functions defined (like some event listener was added to the window), scope will remain active until window closes (if it is not destroyed when window closes, it's memory leak situation by definition) because scope created by listener will reference self-invoking function (as I said in the article, when function is defined, it stores reference to its parent scope). Listener function scope will be active until window is opened.


      ***

      Setting to "null" is necessary in case when variables are global, because they will remain even when window closes (because, they are global variables and they "live" until program runs). So, something from JS side will reference a window even after it will be closed and because of that, TiWindow won't get released (and its listeners also). Setting it to "null" will remove reference from TiWindow and therefore it can be marked from garbage collection.



      Hope this help and if you have more questions feel free to ask. :)

      Delete
    2. Hey, thanks for your quick reply.
      You said that "variables are local variables in function scope created by self-invoking function" but isn't this also the case for variables declared via var = xyz in normal functions?
      When did the function in your last example finish? right after the win.open() command oder after closing the window? How can i close the window in a self calling function if i don't have a reference to the window from outside the function?
      What if I need a reference to the window during the execution or execute some other thinks if the window is closed etc...? Then i will need a reference and an eventlistener oder not? If I will use your modified version I can't access the win, right?
      I hope that you understand my questions :)

      Kind regards
      Fabs

      Delete
    3. Yes, that also stands for "normal" functions. Note that self-invoking function is "normal" function that gets executed right away.

      function() {} //"normal" anonymous function declaration

      (function() {}) //function expression

      (function() {})() //function invocation



      Function in my last example finished with execution right after last statement (win.open();).
      You cannot close window from outside that function. Variables defined in some function are not visible from their parent scopes (it's the way JS functions):

      (function() {
      var test = {};
      })();

      test; //undefined


      But you can from inside:

      (function() {
      ...
      var button = Ti.UI.createButton({ title: 'Click me!' });
      button.addEventListener('click', function() {
      win.close();
      });
      ...
      })();

      In this case, scope created by self-invoking function will not close when it finishes with execution because it has child scope (button event listener). It will remain active until window closes. When window closes, button's event listener will be removed and scope created by self-invoking function now can be closed.


      I don't understand last part, could you explain them a bit, or provide some examples.

      Delete
    4. Hey, you are quick :)
      Forget the last part. You answered it with your last post.
      It is now becoming increasingly clear but i want to understand it to nearly 100%.
      A Simple example.

      var mainWin = Ti.UI.createWindow({backgroundColor:'0F0'});
      var secWin = Ti.UI.createWindow({backgroundColor:'00F'});

      var button = Ti.UI.createButton({ title: 'Click me!' })
      button.addEventListener('click', function() {
      secWin.open();
      });

      //self calling function
      (function() {

      var somevar = "this var is still available until the button is clicked";
      var secButton = Ti.UI.createButton({ title: 'Click me!' })
      secButton.addEventListener('click', function() {
      secWin.close(); // window closes and secButton eventlistener will be removed because???
      });
      secWin.add(secButton); //right after this command the function is finished but scope is still there
      })();
      mainWin.add(button);
      mainWin.open();

      So, until i don't click the secButton the scope of the self called function is alive and every var (like somevar) is there and allocates memory.
      Now, when i click the button the window will be closed and the button listener will be removed automatically and the scope id also destroyed?
      Why is the listener removed automatically, because the parent who holds the button is closed and every child gets killed?

      Kind regards fabs

      Delete
    5. In this particular case, "secButton" and its event listener won't be GCed because "secWin" is global variable that holds reference to TiWindow (and it hods reference to TiButton since it's its child). TiWindow can be closed, but TiWindow will be released from memory when nothing references it from JS (and in this case, global variable reference it). Because of that, it is wise to avoid usage of global scope.

      If all variables were local variables in function scope, then "secWin would be GCed, including "secButton" and its event listener.


      Listener is removed automatically because it is attached to particular element (TiButton) and it must be removed once that element is removed (otherwise it's memory leak situation by definition).

      Delete
  9. Damn, my fault, sure it's global. Here the "i think" right version!?


    var mainWin = Ti.UI.createWindow({backgroundColor:'0F0'});

    var button = Ti.UI.createButton({ title: 'Click me!' })
    button.addEventListener('click', function() {
    //self calling function
    (function() {
    var secWin = Ti.UI.createWindow({backgroundColor:'00F'});
    secWin.open();
    var somevar = "this var is still available until the button is clicked";
    var secButton = Ti.UI.createButton({ title: 'Click me!' })
    secButton.addEventListener('click', function() {
    secWin.close(); // window closes and secButton eventlistener will be removed because???
    });
    secWin.add(secButton); //right after this command the function is finished but scope is still there
    })();
    });

    mainWin.add(button);
    mainWin.open();

    Here secWi, secButton, somevar ... are GCed after the button is clicked, right?
    Regarding your last sentence, the Button gets remove because the window is closed right? If so, i think i finally got it.

    Kind regards
    Fabs

    ReplyDelete
    Replies
    1. There is no sense in putting self-invoking function inside event listener. By having event listener, usage of global scope is avoided. When "mainWin" closes, "button" and its event listener will close also, destroying "secWin" also.

      Wrapping all your code in self-invoking function could have sense.


      Yes, Button will be released from memory when Window closes (if nothing else from JS references it) and if nothing else from JS references it.

      Delete
    2. "Wrapping all your code in self-invoking function could have sense."
      Got it. Many many thanks.

      Can i asked one more question regarding the ImageView in Titanium and its memory allocation?

      Delete
    3. You can ... but I don't know what difference could that make. :)

      Delete
    4. I have a simple app with a window containing a button and a view. If the button is clicked an imageview is added to the view.
      I use Apple Instruments Activity Monitor to monitor the memory allocation of this app.
      Right after starting it shows the usage of 29.58 MB of Real Mem . If i click the button and add the imageview the usage is 30.82 MB, this is 1,24MB more but the image size is just 609x308 pixels and 39KB. Isn't this a litte bit to much overhead?
      If I add an empty imageview of that size (609x308) it uses only 0,22 MB more memory.
      So, I expect that an imageview with an 39KB image uses 39KB+ the 220 KB of the empty view.
      Is it normal that an imageview needs so much memory for so small images?

      Kind regard
      Fabs

      Delete
    5. Don't know, ask in Q/A (or report JIRA issue, it seems much).

      Delete
  10. Thanks for the article. This was the best doc I found regarding GC on Ti, and your explanations on the replies was as important as the article itself.
    Now I can spend my time by doing the right stuff.

    ReplyDelete
  11. Thanks for the article, it was extremely helpful :) I am still having a problem with tabGroups in my App. I have a situation where I am closing and reopening tabGroups, and the elements of the old tabGroup remain in memory. I am calling tabGroup.close() and setting it to null, but the memory still gets higher and higher every time. Any ideas?

    ReplyDelete
    Replies
    1. Hi.

      Without seeing your code, it's hard to tell. The best is to create small test case that shows the issue and ask the question in AppC Q&A.


      Cheers, Ivan.

      Delete
  12. Thank you for the article, Ivan! It is interesting and helpful. Hope to read more from you!

    ReplyDelete
  13. Thanks Man this Article helped Alot. I spent 2 weeks and tried different techniques e.g memory pooling, Setting variables to null, removing listeners. I should avoid App-level listeners and global variables.

    Thanks Again

    ReplyDelete
  14. Hi Ivan!! Thanks for the help. You are simply great. I have one memory leak. Hope you can help me out. We have designed a quiz game. App crashes on relaunching. Below are the reproduced steps, (for iOS)

    1. Start the app
    2. Minimize the app
    3. Remove the app from task bar
    4. Launch the app immediately(within 2-3 seconds)
    5. Home screen appears and app gets crashed


    Same issue with the store kit module.
    1. Click on the button to request a purchase
    2. Minimize the app before response comes from the server
    3. Remove the app from task bar
    4. Launch it immediately and app gets crashed showing purchase screen (sometime response/purchase alert comes even after I remove the app form task bar)


    Awaiting your reply.

    Thanks,
    Sushant

    ReplyDelete
    Replies
    1. Hi.


      Hmmm, seems like Titanium bug. You should report it to JIRA.

      Delete
    2. Have you ever worked on store-kit module? How to cancel the purchase request?

      Delete
    3. No, sorry. You can try to ask in AppC Q&A, I'm sure there are people there who had used it.

      Delete
    4. We have not created app structure you suggested. Do you think writing win.open() code in self-invoking function solve the problem?

      Delete
    5. I don't understand the question or the problem?

      Delete
    6. This comment has been removed by the author.

      Delete
    7. We cheked the same issue with iTunes connect device console,
      When I remove the app from task bar and launch it immediately, console says "Ignoring exit of 'apname' as it is pending activation and will be relaunched".

      When I remove the app from task bar and launch it after some time, console says "Exited: Killed: 9 Application 'appname' quit with signal 9: Killed: 9".


      What do you think? Should user let OS kill the app?

      Delete
  15. Hello Evan.. I have a problem,

    In-App purchase is not working on android. We have used In-App billing module. It is giving 'retrieving information from serve.[RPC:S-7:AEC-0]' error.
    Hope you can help me out.

    Awaiting your reply..

    ReplyDelete
    Replies
    1. This error comes when you are filling the wrong user's information/details. Please, enter user's correct information/details...

      Delete