Memory Management

How do I manage memory in my app? How can I fix memory leaks? How does using Alloy alter memory management best practices? We see questions like this in endless variety. In this article, I will lay down the general principles you should follow to hopefully clear up this issue once and for all.

When you’re done with this article, make sure to read the Debug memory leaks with XCode Instruments article to confirm your app indeed doesn’t have any leaks.

The Titanium “layer cake”

Let’s make sure we’re on the same page about Titanium. You write your app in JavaScript and when you create objects (such as buttons, image views, etc.), Titanium creates corresponding native objects. The two-way link between “JavaScript land” and “native land” is the Kroll bridge.

What this architecture means is that we’ll need to consider:

  • How to clean up JavaScript memory
  • How to remove the native proxies for our JavaScript objects

JavaScript garbage collection

JavaScript uses automatic garbage collection to release objects that are no longer valid. Periodically, the JavaScript interpreter sweeps through memory looking for objects to which no references remain. Those are then destroyed to release the memory they were consuming.

JavaScript has two scopes for variables: global and functional. (ES6 adds block scope, but it will be a while before that spec is finalized and implemented.) Global objects can never be collected because a reference always remains. This means you should encapsulate all variables inside a function (or a CommonJS module, which is essentially a function and thus its own scope).

function buildAView() {
  var someView = Ti.UI.createView(); // this is local to the function
  
  someVariable = 42; // OOPS! this is global because no 'var'

  return someView;
}
// better to do this
win.add(buildAView());

// than this
var myview = buildAView(); // because now there's a reference to manage

Native memory clean-up

Titanium will clean up native objects when their JavaScript counterparts are garbage collected. If the JavaScript object remains valid, the native object must also remain valid.

win.close(); // if you did win.add(buildAView()) then this
// will clean up the View's native objects

// otherwise, make sure to do this
myview = null;  // so native objects can be deallocated

Implicit references

Some code patterns and Titanium APIs can create implicit references to variables. Since you didn’t create those references explicitly, they’re easy to overlook leading to memory leaks.

Closures

Closures are sometimes useful, as demonstrated below. However, they do create references to variables that survive past the end of the function that encapsulates them.

// a CommonJS module with a potential leak
function StarRating() {
    var myView = Ti.UI.createView();
    for (var i = 0; i < 5; i++) {
        var star = Ti.UI.createImageView({
            height:'24dp',
            width:'24dp',
            left:'5dp',
            image: '/star.png'
        });
        
        // use a closure to add a click-event listener that will
        // retain a reference to i, denoting which star was clicked
        (function() {
            // index will remain valid and could be a leak
            var index = i;
            star.addEventListener('click', function() {
                setRating(index+1);
            });
        })();

        // Try it, use this code instead and see what i equals
        //star.addEventListener('click', function() {
        //  Ti.API.info('You clicked star ' + i);
        //});
        
        myView.add(star);
    }
    return myView;
}
module.exports = StarRating;

Global events

Some Titanium events are global to your application. These include location and orientation listeners, among others. Make sure to set these event listeners only once, preferably from your index controller (index.js or app.js).

var myView = Ti.UI.createView();

function updateView(e){
  if(e.source.isPortrait()) {
    myView.left = 2;
  } else if (e.source.isLandscape()) {
    myView.left = 10;
  }
}

// Because Ti.Gesture is global, this creates a global reference to your view
Ti.Gesture.addEventListener('orientationchange', updateView);
// make sure to later remove it or you've created a memory leak
Ti.Gesture.removeEventListener('orientationchange', updateView)

Images

Images are a stumbling point for many developers. They look at the 10kb file and wonder why adding a hundred of them to a view causes memory issues. Then they blame Titanium.

To be shown on screen, every image must be converted to a bitmap. The size of the file is unrelated and unimportant. For every pixel in your image, the computer must maintain at at least four bytes of information: red, green, blue, and alpha channels. Consider a 200px square image:

200 by 200 px image
= 40,000 pixels x 32 bits per pixel
= 1,280,000 bits
= 160,000 Bytes
= 156.25 KB
= ~1/6th MB per image

Keep in mind that your app gets very little memory. It could be as little as 12 MB for everything (the Titanium framework components, your code, your images, etc.). 24 MB is probably more typical, but that’s still tiny compared to desktop environments.

  • Images are uncompressed bitmaps in memory even if they’re stored in a compressed file format (JPG, PNG, etc.)
  • File format doesn’t matter
  • Lazy load images — load them only when you need them
  • Then unload / destroy images as soon as you no longer need them

Garbage collection speed

Garbage collection in JavaScript happens independently, and often more frequently than in the native side. The way it’s handled on iOS, Android, MobileWeb, BlackBerry, etc. all differ. Know your target platforms!

Android, for example, can have problems when your code quickly allocates/deallocates objects (e.g. in a tight loop). The JavaScript side can keep up with memory deallocation, but Java’s GC routines are notoriously lazy. You can end up with many objects ready to be deallocated (or even deallocated in the JavaScript environment) that Java doesn’t get around to collecting. All of a sudden your app exhausts available memory (= crash) leaving you to blame Titanium for something that was your doing.

Alloy

How does Alloy change things? It doesn’t really. Each controller in Alloy is its own CommonJS module. This simplifies your work as your code is automatically more modular with Alloy. You still need to keep the other recommendations in mind: avoid globals, null out objects when you’re done with them, etc.

// when $.win closes, it can take the foo controller objects with it
$.win.add(Alloy.createController('foo').getView());
 
// don't create references you don't need, meaning don't do this
var myFoo = Alloy.createController('foo');
// unless you need to modify myFoo before adding it to your window
$.win.add(myFoo.getView());

// if you've created a myFoo reference, you should
myFoo = null; // when you're done with it

Alloy Globals

Alloy offers a built-in global object, Alloy, which has a property called Globals that you can use to store values available throughout your app. Use it sparingly.

// if you have just a few global values to reference
Alloy.Globals.myVariable = whatever;
 
// if you have a need for more global variables, use a CommonJS module
myObject.someVal = require('yourconstants').myVariable;

Not sure you understand CommonJS modules and the patterns that technique offers? Make sure to read the CommonJS Modules in Titanium guide.

Destroying Alloy Collections

If you use Alloy’s View/Model binding feature, you must call the $.destroy() function when closing a controller to prevent potential memory leaks. The destroy function unbinds the callbacks created by Alloy when the model-view syntax is used. For example, the code below calls the destroy function when the Window’s close event is triggered.

$.win.addEventListener("close", function(){
    $.destroy();
} 

Recommendations and guidelines

So, here are my recommendations in list form:

  • Don’t use global variables. Global variables cannot be garbage collected (because there’s always a reference to them). If you think you need global variables, think again.
  • Always use var when declaring variables (they’re globals if you don’t).
  • Don’t create references to objects (use anonymous object instantiated inline) unless you need to.
  • null out objects when you’re done with them.
  • Encapsulate view creation in commonJS modules which can be instantiated and then deallocated later (or use Alloy).
  • Destroy collections ($.destroy()) when using view/model binding & you’re done with the associated view.
  • Don’t use closures, and watch out for implicitly-created closures, unless you know what you’re doing and later deallocate those objects.
  • Set global event listeners (Ti.App.addEventListener, Ti.Geolocation.addEventListener, etc.) with care &emdash; in your top-level controller, remove as soon as you can, never more than once, etc.

Perhaps most importantly, learn JavaScript. It’s not Java, C#, Ruby, etc. It works the way it works. Don’t fight it and try to force-fit other languages’ techniques into JavaScript. Be all zen-like and accept it for what it is. Learn about scoping, hoisting, prototypal inheritance, closures, etc. Yeah, it’s a lot to learn. But you’re a developer, you can do it.

Modifying Titanium proxies

Not strictly memory management related, but germane to this article: don’t modify Titanium proxy objects (any object that begins with Ti.*). As you should know by now, they represent native objects. The Kroll bridge maintains a link between the object living in JavaScript-land and the corresponding native object(s). Modifying the JavaScript proxy object can break the link between “lands” or corrupt the native objects. We have for years recommended that developers avoid doing things like Ti.App.myCustomVariable = 'stuff I want to be global' but people do it all the time. Don’t do it. This can cause a crash or hang your app.

OMG, Titanium (or Alloy) has a leak!

So are there memory leaks in Titanium? Probably. But when found and reported in Jira, such issues get prompt and high-priority attention. The last valid one I know of took less than two days from reporting to being fixed and in the nightly build, with much of that time verifying that it was a real bug and not a developer issue. In my personal experience, 99% of “Titanium memory leaks” have been the fault of developers doing unwise things with their code.

If you find something you think is a leak in Titanium, check your code carefully. After confirming you’ve followed all the best practices described above and eliminated your code as the problem, create the smallest possible app that demonstrates the leak. Then open a Jira ticket. Use a clear and descriptive title, and describe the problem as clearly as you can. Make sure to attach your sample project. Appcelerator’s engineers will look into your report as quickly as we can.

JavaScript developer at Appcelerator, working on the Tools team (primarily Alloy). Formerly, I was the Master Trainer and Curriculum Developer for Appcelerator.


Comments

  • Iñaki Abete

    Code examples are not being shown.

  • Fokke Zandbergen

    Sorry, my fault… fixed it :)

  • RadioHead

    good post

  • Carlos C

    I’m not (still) an experienced javascript programmer, sorry.

    It isn’t clear to me if the closure example is something to do or to avoid.

  • Larrie Wilson

    For the closure example, would the following be better:

    (function(index) {
    star.addEventListener(‘click’, function() {
    setRating(index+1);
    });
    })(i);

    which would not retain the reference to i?

  • Vinicius Oliveira

    And use:
    win.url = “MyScreen.js”;

    Is a good or bad idea ?

  • Joseph Sachs

    Much appreciated @Tim, I use most of these suggested, most of the time… Null-ing unused objects still bugs me to remember… :)

    Thanks for the reminder.

  • Luis Hurtado

    I have the same question Larrie. If you (or somebody else) find the answer please let me know.

  • Vui Nguyen

    This was an excellent post, Tim! Thanks so much for writing it and putting all these ideas together.

    I also use callbacks to avoid memory leaks, as a substitute for global event listeners. That way, you don’t have to keep track of where you added the global event listener and remember to remove it somewhere else in your code, to avoid the memory leak. I might have actually learned this trick when I took the advanced Titanium course from you last year. :)

  • Iñaki Abete

    Same here!

  • Demostenes

    No doubt, great post!

  • Ranjith Kumar Nagella

    In regards to Images section – since, its taking up to 1/6th mb on memory every time an image is rendered. Do you think we can somehow reduce this with “web fonts”. ?

  • M

    Hi, I have got one question for you… I don’t know if I am the problem or what but I reported two or three memory leaks (I don’t remember exactly because it was months ago) and it looks that they are just ignored, seriously I hate it. It would be good if somebody could just have look at it… Few days ago I have discovered another one ,but what’s the point create a ticket if it is going to be fixed probably in X years (Somehow by itself when API will be deprecated or something).

  • Arthur R

    Hi Tim,

    I’ve always wondered. Do addEventListeners I create in a controller do get removed when I call destroy() on the controller they’re into?

    Thanks.

  • Fokke Zandbergen

    No, $.destroy() only takes care of removing listeners to global collections created by data-binding attributes in the XML.

  • Arthur R

    Ok so should I remove listeners in a custom function called before destroying the controller or is it useless?

    Thanks!

  • Guile

    Knowing this rules I take the decision to create the cleanup function as soon as I create a new controller.
    Then every time I write a “addEventListener” in the controller, I put the matching “removeEventListener” in the cleanup.

  • Guile

    Concerning images I have a question for what I can’t find any answer : how to destroy images as soon as they’re no more necessary.
    Should I destroy by hand the ImageView (remove($.img) and $.img = null) created with an alloy view?
    How can I “unload” image that are out of the screen and “reload” those that are in the screen?
    Does anyone have the answer?

  • Fokke Zandbergen

    I don’t really like these kind of optimisations. Better just close windows when you don’t need them and reopen if you do. For a ScrollableView you could add empty views at first and then use a listener to add/remove the actual content as the view scrolls in.

  • Fokke Zandbergen

    Good plan! Just as a note, you don’t need to remove event listeners to “local” views.

  • Guile

    Thank you for your answer. I have to do a ScrollView with (potentially) infinite scroll : about 6 to 10 photo on screen, but a ScrollView that may contain 200 photos.
    The only solution I guess is to remove the infinite scroll and to make a ” paged list” (which is not what I was initially meant to do :-( )

  • Guile

    @FokkeZ What do you mean with “local views” ? Do you have any gist or thing illustrating things like that? Thank you!

  • Fokke Zandbergen

    @Guile, I mean views in the same context that will be destroyed at the same time as the listener, so no need to remove the listener.

  • Galgo Dev

    im using clasic titanium
    is the same way to use?

  • jayesh1989

    Don’t use global variables mean Alloy.Globals.Map it’s bad but if i user Alloy.CFG.COLOS.Black kind variable from config.json than it take memory or not ?