Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AngularJS: How to correctly work with Directives, Scopes and Bindings to avoid memory leaks?

I'm trying to find a solution for an AngularJS app memory leak. Because I'm new to the AngularJS world I don't really know where to start and where to correct and optimize my code.

I'm trying to give you a little description of the application first. Afterwards I'll post some memory statistics I could measure. I used three diagnostic tools to measure the used memory: Windows (7) Task Manager, Firefox about:memory and the Firefox Extension MemChaser.

Application

  • the application is embedded in a page, should be loaded once and stay there for at least 24 hours
  • the data is continiously loaded in an interval of 1 to 60 minutes via an $http() Ajax request
  • the data is a JSON object with some hierarchical structure
  • for every layer of this hierarchy there is a custom component (with an isolated scope)
  • selected parts of the data will be noticed and kept beyond an Ajax request
  • most of the directives load an html file (via $http()) to compile it as there template

Statistics

  • Windows Task Manager

    The memory used by Firefox increases by 50 - 100 MB per hour.

  • about:memory

                                       Size (MB)   20 min diff  Size (MB)
    JS-Main-Runtime                     32         36/+113%      68
    JS-Main-Runtime-GC-Heap-Committed   20         27/+135%      47
    Heap-Allocated                      54         29/+54%       83
    Heap-Committed                      63         28/+44%       91
    JS-GC-Heap                          31         26/+84%       57
    Private                            156         58/+37%      214
    Resident                           175         62/+35%      237
    VSize                              509         86/+17%      595
    
  • MemChaser 0.5.2.1

                   12:17   12:27   12:57   13:17
    Resident (MB)  140     164     243     270     
    iGC (ms)        42      24      40      42
    CC (ms)          3      53     206     286
    

    Resident: Memory used by the process that is present in the physical memory. iGC: Duration of the last garbage collector activity. CC: Duration of the last cycle collector activity.

These results are pretty dramatic and it seems like the cycle collector gives the best hint. If I run my app without the view (just the Ajax request) nothing dramatic happens. If I disable the dynamic template loading there is no distinctive difference to the version with dynamic templates. So it seems like these two topics aren't the cause.

My idea: with every Ajax request new scopes and DOM nodes are created. The DOM nodes may be ereased afterwards but the scopes with there data will propably still be in the memory. Is that a possible scenario and a cause for my memory leak?

So how should I properly work with AngularJS directives, scopes and bindings to avoid memory leaks like these?

I would be very very delighted about any help.

Tobias

like image 983
fea17e86 Avatar asked Oct 30 '13 12:10

fea17e86


1 Answers

I use the AngularJS Batarang Chrome extension to assist in debugging these type of issues. Monitor the Models and Performance tabs of this extension to pick up on any dangling or leaking scopes. Make sure when you do not need a specific scope any more that your $destroy it. E.g. see how ngRepeat does this.

From the documentation:

$destroy()

Removes the current scope (and all of its children) from the parent scope. Removal implies that calls to $digest() will no longer propagate to the current scope and its children. Removal also implies that the current scope is eligible for garbage collection.

The $destroy() is usually used by directives such as ngRepeat for managing the unrolling of the loop. Just before a scope is destroyed, a $destroy event is broadcasted on this scope.
Application code can register a $destroy event handler that will give it a chance to perform any necessary cleanup. Note that, in AngularJS, there is also a $destroy jQuery event, which can be used to clean up DOM bindings before an element is removed from the DOM.

And

$destroy() must be called on a scope when it is desired for the scope and its child scopes to be permanently detached from the parent and thus stop participating in model change detection and listener notification by invoking.

like image 166
Beyers Avatar answered Oct 16 '22 23:10

Beyers