An Usual Day of Memory Leak Analysis With Gotcha

We can click the right link and navigate to that line of code to know that, but it becomes tedious if there are many controllers/constructors having the same name.

Next time when we record another snapshot, we will need to look at it again.

Simply give a more specific name can save a lot of clicks and uncertainties.

More Specific Naming ConventionMuch more clear.

Note that ES6 concise method and arrow function eventually create anonymous function in terms of constructor.

As of ES6, if the name of the constructor is missing like an anonymous function, the name of the variable/property that holds the function will be used.

Name Taken from the PropertyName abcdef Shown in Heap SnapshotHowever, it’s hard to locate where the final step when the constructor function is called and used on which variable/property for AngularJS, as there may be some other processing so we can’t search forcontrollerto find it.

My point here is simply providing the function name to avoid this hassle.

Based on my experience, usually the most common cases for memory leak are:Lexical scope that holds a reference to an outer scope, and this scope is not destroyed even though the outer one is, which is very common for event binding or callbackDetached DOM elementThe first case of event binding and callback almost exists in every single large application.

$rootScope Holding a Reference of the scope of ComponentACtrlThis is a common AngularJS memory leak example.

However, the same concept applies in general.

When the scope of componentA is destroyed, AngularJS clears everything in the scope and let the engine to collect the garbage and reclaim memory.

There is a special handling for IE 9 too (unsurprisingly).

AngularJS Cleaning Up Scope #1AngularJS Cleaning Up Scope #2However, $rootScope needs the scope of componentACtrl as its event toggle-event has a call back that has the reference to this scope.

It doesn’t matter if it uses any variable inside the callback or not.

It matters only if it has a closure of the outer scope.

There are a few scenarios in this case and they can have different outcomes:1.

Holding a reference of the controller scopeHolding a reference of the controller scopeAs the outer function has this.

a = 'aaa', it needs and holds this, which is the scope of the controller ComponentACtrl because of the arrow function.

After destroying the component, the memory of the whole controller scope cannot be released.

Scope of ComponentACtrl cannot be releasedThe distance is also farther away from window object compared to ComponentBCtrl and ComponentCCtrl.

If this is not used, we won’t see ComponentACtrl in here.

Then it comes to the second case.

2.

Holding a reference of the outer functionHolding a reference of the outer functionIf we have no other statements/assignments in the outer function besides the event registration, and we don’t use arrow function, ComponentACtrl will not show up in the snapshot.

Does that mean memory leak problem is solved, and hence closure doesn’t occur?.Obviously not.

It’s just that there’s no constructor name can be found for this function.

Record a snapshot before destroying and another one after creating componentA, and compare the result .

Delta Equal to +1As I expect that the changes of some of the items stayed in memory should be increased by exactly 1, so I sort it by Delta and look for it.

Closure and Context Increased by oneExactly as it says, closure is increased by one.

If we also check (closure) for case 1, it actually increases by two, because besides the callback function itself, it also has the reference to all other functions on the controller scope, which includes$onInit.

3.

No callbackWhen there’s no callback for the event, no closure is needed and thus no memory leak will happen.

The idea is similar to detached DOM.

A DOM is considered as detached when it doesn’t exist in the DOM tree but it has a reference in memory.

The meaning of detached in here is not exactly the same as Virtual DOM, which is used to render selectively based on state changes.

Detached DOM refers to the native DOM element object being detached.

Normally if we keep a reference in a lexical scope and do not store it outside of the scope, it will be garbage collected automatically.

Up until this point, I still haven’t shared my gotcha of that memory analysis.

I checked for event binding and any DOM element manipulation and references (there were a lot…).

They all seemed good.

What went wrong?.I stared at this for a while and took a break.

Solutions usually come up after my mind flushed out for 15 minutes.

First idea I came up with was using a dirty way to confirm if it actually leaked from this component — commented out all the code inside the controller.

Not surprisingly, the memory leak issue disappeared, so that confirmed my very first process of identifying this component correctly.

I uncommented it, took a snapshot, and looked at the snapshot again.

This time I saw something that I didn’t see before.

The distance of the item was ‘ — ‘, and it’s said “DevTools console” right below.

Leak in DevTools consoleMy actual case had way more items in the snapshot, but still, how could I miss that?.It was all just because of a console.

log(this)!.The console held the reference of the scope for us to click and inspect, and so it had the reference of it (note that console.

log increases JS heap only if we open up the debugger).

Why did we console log the whole scope?.There are a lot of tools and plugins to visualize it already.

It may be convenient during early development, which I won’t do it anyways, but this should be removed when the code is quite stable.

I somehow felt like in the old days of missing a semi-colon in those compile languages, except those told me exactly which line’s missing it.

A side story: there was once we were looking at why a new file with one statement added was not working in our application, and it was all because of missing a semi-colon.

We used Gulp to concatenate all the codes and it thought that the statement was a function call, which used the next file for the parameters, i.

e.

(function(){.

}(module.

exports))(function(){.

})(module.

export); The library of lazy loading caught the error somewhere and lost the initial trace.

That took us an hour to find it out.

Memory leak analysis is fun as it always tests our understanding of a lot of JS concepts.

It also helps us to write better code and reminds us good practices of development.

For the simple demo I referenced in this post, I pushed it to here: https://demo.

kelvinau.

net/console-log-thisStay tuned for my next memory analysis case!.

. More details

Leave a Reply