Key questions
Do I know how much memory my JS app is it consuming?
Has it any memory leak?
Do I know how to look for the answers of questions 1 and 2?
Tip: We will use Chrome Dev Tools. They are awesome!
What is this all about?
- Understand JS memory allocation and GC
- Find and kill memory leaks
- Advanced memory techniques
Why should I care?
- Client side apps in "low-end" devices
- Mobile (Including Firefox OS)
- Chromebooks
- Smart TV Apps
- Set-top boxes
- Server side applications
Does it worth the effort?
Which kind of app do I have?
- Single page applications
- HTML5 Games
- Apps for HW limited environments
- Long-life apps (server side...)
For sure
- Form validation
- Fancy effects
No way
Memory vs.
Performance vs. Development Cost
"Early optimization is the root of all evil" D. Knuth
A real world scenario(Hybrid TV)
- Satellite + OTT Set-top Box
- UI: SVG + JS (~ 80 KLOCs)
- 12Mb Heap in Browser for UI
- Memory footprint evolution across versions6 Mb - 4.5 Mb - 3.9 Mb
- 10+ Memory leaks detected
Understanding memory management
Basic concepts
- Heap memory
- Garbage collection
- Memory leak
- Memory graph
"The heap area is the dynamic memory pool where objects are allocated
when they are created by a program"
"The garbage collector is the process that frees a memory area when the objects allocated in it,
are no longer retained (being used) by the program"
"A memory leak is a memory area that cannot be collected even when it will be
no longer actively used"
"The memory graph is the visual representation of how memory is organized, showing variable values
starting in the root node until leaf nodes"
JavaScript Memory Graph. Allocating memory
// window
var a = 3;
var myObject = {
prop1: a, // It's a copy
prop2: 'A string'
};
var myArray = [
myObject, // By reference
new Date()
];
Removing a value
myArray = null;
myObject = null;
Garbage collection
myArray = null;
myObject.prop1 = null;
// delete myObj.prop1;
myObject = null;
When can the GC collect a node?
"A memory area can be freed when there is no active reference to it"
Local variables of a function when it is executed and returns
Any object whose all references (variables pointing to) have been nullified (or deleted)
Closure scope variables when the function references disappear
-
Event listeners when they are unbounded
DOM Nodes where they are not in the DOM tree and there is no variable pointing to it
What is a memory leak?
"A leak is a memory area that will be never freed by the GC, even when there won't be
further active access to it"
-
Circular references in objects/functions
-
"Global" variables (lists, etc.) never emptied
-
DOM nodes not removed from JS objects
Finding and killing memory leaks
Checklist to detect memory leaks
Check initial memory state (Memory snapshot)
Execute the suspicious action that should not increase memory
Check memory again
If memory is significantly higher, There is a leak
Use Profiles and Timeline to track what is happening
- Amount of DOM nodes
- Retained memory
Tips for working with Chrome Dev Tools
- Disable add-ons (or use Private browsing)
- Clean console (disable logs if possible)
Show me the code!
What's wrong with this?
function testLeak() {
myArray = [];
for (var i = 0; i < 1000000; i++) {
myArray.push(i.toString());
}
return myArray[0];
}
function runTest() {
testLeak();
}
function testLeak() {
var myArray = []; // Don't forget the var here
for (var i = 0; i < 1000000; i++) {
myArray.push(i.toString());
}
return myArray[0];
}
function runTest() {
testLeak();
}
DON'T USE GLOBAL VARIABLES!
[show in browser]
Finding the root of a memory leak
The two steps technique
Init in clean state
Take a heap snapshot
Execute a sequence of steps and come back to initial state
Take another snapshot
Check differences between Snapshot 1 and Snapshot 2
Understanding the results
-
Distance: number of jumps from the root (window)
-
Shallow size: size of the object itself
-
Retained size: size of the object plus the objects it is referencing
-
Retainers: list of objects retaining this object/node
-
Yellow Node has a JavaScript reference
-
Red Node referenced by a yellow node
Finding the root of a memory leak
Record heap allocations
Start recording a heap allocation
Execute suspicious actions
Analyze results in timeline
Blue lines are allocated memory
Grey lines are freed memory
And what about Node.js?
Use heapdump
var heapdump = require('heapdump');
heapdump.writeSnapshot('/tmp/step1.heapsnapshot');
executeLeakyAction();
heapdump.writeSnapshot('/tmp/step2.heapsnapshot');
Load *.heapsnapshot in Chrome Profile
1. Global variables
See previous slide...
Not only global, but also long-live objects:
- Module pattern
- Memoized functions
- Caches
2. DOM Leaks
var ToDoList = function() {
this._items = [];
this._itemIndex = 0;
this._mainNode = document.getElementById('main');
};
ToDoList.prototype.addItem = function() {
var li = document.createElement('li');
li.innerHTML = 'Item ' + this._itemIndex++;
this._items.push(li);
this._mainNode.appendChild(li);
};
ToDoList.prototype.removeAll = function() {
for (var i = 0, len = this._items.length; i < len; i++) {
this._mainNode.removeChild(this._items[i]);
}
};
[show in browser]
Remove all references to DOM nodes
3. Closures
function testLeak() {
var myArray = [],
mySecondArray = [1, 2, 3, 4],
anotherObject = {a: 'b'};
for (var i = 0; i < 1000000; i++) {
myArray.push(i.toString());
}
return function leakClosure() {
return myArray;
};
}
var closure;
function createMemory() {
closure = testLeak();
}
function freeMemory() {
closure = null;
}
[show in browser]
Closures retain scope variables
3.1 Event listeners
function registerEvent() {
var text = new Array(10000000).join('x')
var node = document.getElementById('div');
node.addEventListener('click', function() {
return text;
});
};
Remove event handlers when no longer needed
3.2. Closures inside objects / Timers
var object;
function createMemory() {
object = {
prop: new Array(10000000).join('x'),
run: function() {
var that = this;
setTimeout(function() {
that.run();
}, 10000);
}
};
object.run();
}
function freeMemory() {
object = null;
}
[show in browser]
Take care of "that" in closure scope.
Advanced memory management
v8 Hidden classes. Fast objects
- Chrome "compile" JS and create faster hidden classes that represent app objects
- When the structure of an object is changed by deleting properties, the object becomes slow,
and takes much more memory
Fast object
var fastObject = {
prop1: 'string',
prop2: new Date()
};
// Fast operation
fastObject.prop1 = null;
Slow object
var slowObject = {
prop1: 'string',
prop2: new Date()
};
// Slow operation
delete slowObject.prop1;
See it in action
The cost of Garbage Collection
- Every time the heap area is exhausted the garbage collector frees memory
- Every memory allocation makes next GC closer
- GC can take milliseconds to run: affects smoothness of applications
- Higher latency
- Less FPS (Be careful game developers)
Introducing Memory Pools
-
Use a memory pool for keep objects always in memory and avoid GC
-
Increase setup time but app runs smoother
-
Take care of consuming too much memory
// It creates 1000 objects and store them in a local cache
var pool = new SimpleMemoryPool({prop1: null, prop2: null}, 1000);
// Get an object from the pool, and assign the desired values
var myObject = pool.getInstance('an string', new Date());
// myObject = {prop1: 'an string', prop2: new Date()}
// When I don't need the object anymore, release it to the pool again
pool.release(myObject);
// prop1 and prop2 are nullified and object goes back to the pool
Automatizing memory tests. Logging your memory
Use Chrome performance.memory API
-
jsHeapSizeLimit: maximum heap size
-
usedJSHeapSize: current memory allocated
-
totalJSHeapSize: allocated heap, including free space
Enable memory logging with --enable-precise-memory-info
Tip: Automatize your memory tests (Selenium...)
Homework
- ES6 WeakMaps
- V8 Young generation objects vs. Old generation
- JS execution loop, DOM layout and reflows
Useful tips when developing a big JS application
- Don't rely too much on Internet libraries
- Don't keep DOM references in code when don't need them
- The less global state, the better
- Take care about scope
- Use data caches
- ...but take care of the amount of memory (MRU caching)