I'm investigating the cause of a memory leak having to do with the nodes count, but I'm just getting confused when I look at what angular is doing, because the results are not consistent enough to draw a clear line of what is happening.
I've created a really simple app, it has 1 controller and 2 views. Each view use the same controller:
core.controller('CoreCtrl', [function() {
var core = this;
core.helloWorld = function() {
console.log('hello world');
};
}]);
core.config(['$routeProvider', function($routeProvider) {
$routeProvider
.when('/test1', {
template: '<a href="#/test2" ng-click="core.helloWorld()">Go to test 2</a>',
controller: 'CoreCtrl',
controllerAs: 'core'
})
.when('/test2', {
template: '<a href="#/test1" ng-click="core.helloWorld()">Go to test 1</a>',
controller: 'CoreCtrl',
controllerAs: 'core'
})
.otherwise({redirectTo: '/test1'});
}]);
I then record a timeline in the Chrome dev tools, alternating between switching between the views quickly and then every other second, as you can see clearly in the graph.
This is the results:
As you can see after the first period of switching quickly, the nodes count drop massively, then I start switching views more slowly and then fast again. This time after switching more quickly the drop of nodes count is not even close to that of the first drop, as well as the nodes continuing to go up after that.
There are 2 things here that are off to me:
Why does the nodes and listeners count keep going up after you switch view, only dropping after a certain period? Shouldn't this be more of a sawtooth pattern,
- _ - _ - _ - _-
since each view switch should do a cleanup of the nodes and listeners?Why isn't the second drop as big as the first one?
To me this seems like something can't keep up with cleaning up after itself when switching between the views. Or something isn't being cleaned up at all, which is odd since there are no listeners that would require manual cleanup.
Can anyone make any sense out of these test results?
The memory does not go down immediately since you can't really control when the garbage collection will occur. Usually, the browser only cleans up the memory when it needs to allocate more space and it needs to free up some room first.
That is why, for debugging purposes, you have access to the "Collect garbage" button in the DevToolbar (). You should press this button at the beggining and end of every recording, and in your case, you could even press it everytime you finished switching the pages. This way you will have the guarantee that you are looking at the used memory instead of the occupied memory.
This is likely because of the same cause.
I profiled your code and I got the following timeline after manually triggering the garbage collection:
As you can see, it seems like all the event listeners and nodes were correctly cleaned up.
If, in spite of this, you are still experiencing lag on a slow machine, it is likely this happens because they run out of memory faster and need to perform garbage collection more often.
As mentioned in the DevTools docs:
It's not really clear if this is the particular problem your app is facing though, so you would need to investigate further. It's likely something more complex is going on in your app than in this simple example.
I found the Chrome DevTools CPU throttling feature very useful in getting the same slowness that my users were having with the application. Also, this talk by the Gmail team was great at explaining how to track down these issues and showing the relation between memory usage and performance of an app.