Finding and
debugging
memory leaks in JavaScript with
Chrome DevTools
memory leaks in JavaScript with
Chrome DevTools
$ whoami
$ aboutthis
Gonzalo Ruiz de Villa
@gruizdevilla
I co-founded & work @adesis
This presentation was make for my workshop at #spainjs 2013
What is a memory leak?
Gradual loss
of available computer memory
when a program
repeatedly
fails to return memory
that it has obtained
for temporary use.
My users have laptops
with 16GB of RAM.
So, why should I care?
Common belief
More memory === Better performance
Reality
Memory footprint
is strongly correlated with
increased latencies and variance
Nothing is free:
you will always pay a price
for the resources you use
So, let's talk about memory
Think of memory as a graph
Three primitive types:
Numbers (e.g, 3.14159...)
Booleans (true or false)
Strings (e.g, "Werner Heisenberg")
They are always leafs or terminating nodes.
Everything else is an "Object"
And what about Arrays?
An Array is an Object
with numeric keys.
The memory graph
starts with a root
It may be the window object of the browser, or the Global object of a Node.js module.
What does get GC?
Retaining path
Dominators
Some facts about
the V8 Garbage Collector
Generational
Collector
Age of a value
Young Generation
Old Generation
The age of a value: number of bytes allocated since it was allocated.
- Splited in two spaces: named "to" and "from"
- "to space": very fast allocation
- filling the "to space" triggers a collection:
- "to" and "from" swap
- maybe promotion to old generation
- ~10ms (remember 60fps -> ~16ms)
- Old generation collection is slow.
"To" and "From" spaces
Remember: triggering a collection pauses your application.
Some de-reference
common errors
"o" becomes an SLOW object.
It is better to set "null".
Only when the last reference to an object is removed, is that object eligible for collection.
A word on "slow" objects
-
V8 optimizing compiler makes assumptions on your code to make optimizations.
-
It transparently creates hidden classes that represent your objects.
- Using this hidden classes, V8 works much faster. If you "delete" properties, these assumptions are no longer valid, and the code is de-optimized, slowing your code.
Fast Object
Slow Object
function SlowPurchase(units, price) { this.units = units; this.price = price; this.total = 0; this.x = 1; } var slow = new SlowPurchase(3, 25); //x property is useless //so I delete it delete slow.x;
"slow" should be using a smaller memory footprint than "fast" (1 less property), shouldn't it?
function FastPurchase(units, price) { this.units = units; this.price = price; this.total = 0; this.x = 1; } var fast = new FastPurchase(3, 25);
"fast" objects are faster
REALITY: "SLOW" is using 15 times more memory
Timers
Timers are a very common source of memory leaks.
Look at the following code:
If we run:
With this we have a memory leak:
var
buggyObject = {
callAgain: function () {
var ref = this;
var val = setTimeout(function () {
console.log('Called again: '
+ new Date().toTimeString());
ref.callAgain();
}, 1000);
}
};
buggyObject.callAgain();
buggyObject = null;
Closures
Closures can be another source of memory leaks. Understand what references are retained in the closure.
And remember: eval is evil
var a = function () {
var largeStr =
new Array(1000000).join('x');
return function () {
return largeStr;
};
}();
var a = function () {
var smallStr = 'x',
largeStr =
new Array(1000000).join('x');
return function (n) {
return smallStr;
};
}();
var a = function () {
var smallStr = 'x',
largeStr =
new Array(1000000).join('x');
return function (n) {
eval(''); //maintains reference to largeStr
return smallStr;
};
}();
DOM leaks are bigger than you think
When is the #tree GC?
var select = document.querySelector; var treeRef = select("#tree"); var leafRef = select("#leaf"); var body = select("body"); body.removeChild(treeRef); //#tree can't be GC yet due to treeRef treeRef = null; //#tree can't be GC yet, due to //indirect reference from leafRef leafRef = null; //NOW can be #tree GC
#leaf maintains a reference to it's parent (parentNode), and recursively up to #tree, so only when leafRef is nullified is the WHOLE tree under #tree candidate to be GC
Rules of thumb
Unbind event listeners
Manage local cache
Unbind events that are no longer needed, specially if the related DOM objects are going to be removed.
Be careful with storing large chunks of data that you are not going to use.
Object Pools
objects, reuse them with object pools.
(for example, cleaning used objects)
Three key questions
-
Are you using
too much memory?
- Do you have
memory leaks?
- Is your app GCing
too often?
too much memory?
memory leaks?
too often?
Knowing your arsenal
Browser Info
You can monitor their activity to detect unexpected use of memory
(only in Chrome)
jsHeapSizeLimit: 793000000, usedJSHeapSize: 27600000, totalJSHeapSize: 42100000
}
usedJSHeapSize
totalJSHeapSize
the amount of memory that JavaScript has allocated (including free space)
the amount of memory currently being used
If usedJSHeapSize grows close to jsHeapSizeLimit there is a risk of:
I mean...
Chrome DevTools
Ctrl+Shift+I
⌥⌘I
Memory timeline
Memory Profiling
Reading your results
Summary
EYE-CATCHING THINGS
IN THE SUMMARY
If almost all the objects of the same type
are at the same distance,
and a few are at a bigger distance,
that's something worth investigating.
Are you leaking the latter ones?
MORE EYE-CATCHING THINGS
IN THE SUMMARY
AND
the objects they are referencing.
using most of the memory.
A TIP ABOUT CLOSURES
It helps a lot to name the functions, so you easily distinguish between closures in the snapshot.
function createLargeClosure() {
var largeStr = new Array(1000000).join('x');
var lC = function() { //this IS NOT a named function
return largeStr;
};
return lC;
}
function createLargeClosure() {
var largeStr = new Array(1000000).join('x');
var lC = function lC() { //this IS a named function
return largeStr;
};
return lC;
}
Switching between snapshots views
Understanding node colors
with yellow background
You can force GC from Chrome DevTools
Memory leak pattern
The 3 snapshot technique
Rationale
Your long running application is in an stationary state.
What do we expect?
New objects to be constantly and consistently collected.
Let's say we start from a steady state:
Checkpoint #1
We do some stuff
Checkpoint #2
We repeat the same stuff
Checkpoint #3
Again, what should we expect?
All new memory used between Checkpoint #1 and Checkpoint #2 has been collected.
New memory used between Checkpoint #2 and Checkpoint #3 may still be in use in Checkpoint #3
The steps
- Open DevTools
- Take a heap snapshot #1
- Perform suspicious actions
- Take a heap snapshot #2
- Perform same actions again
- Take a third heap snapshot #3
- Select this snapshot, and select
"Objects allocated between
Snapshots 1 and 2"
"Objects allocated between
Snapshots 1 and 2"
The 3 snapshot technique
evolved
Simpler & more powerful
but...
Do you have Chrome Canary installed?
Brand new feature:
Record Heap Allocations
Blue bars : memory allocations. Taller equals more memory.
Grey bars : deallocated
Let's play!
You can get the code from:
Thank you!
gonzalo.ruizdevilla@adesis.com
@gruizdevilla
(btw, we are hiring!)
memory
By Gonzalo Ruiz de Villa
memory
- 100,382