Finding and 
memory leaks in JavaScript with
Chrome DevTools


$ whoami

$ aboutthis

Gonzalo Ruiz de Villa

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
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


Memory footprint

is strongly correlated with

increased latencies and variance

Nothing is free:

(cheap or expensive)

you will always pay a 
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 cannot reference other values.
They are always leafs or terminating nodes.

Everything else is an "Object"

Objects are associative arrays (maps or dictionaries)

So, the object is composed of a collection of (key, value) pairs

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.

You don't control how this root object is GC

What does get GC?

Whatever is not reachable from the root.

Retaining path

We call a retaining path any path from GC roots to a particular object


Node 1 dominates node 2
Node 2 dominates nodes 3, 4 and 6
Node 3 dominates node 5
Node 5 dominates node 8
Node 6 dominates node 7

Some facts about 

the V8 Garbage 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

Be careful wit the delete keyword.

"o" becomes an SLOW object.
var o = {x:"y"}; 
delete o.x; 
o.x;   //undefined

var o = {x:"y"}; 
o = null
o.x;   //TypeError

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; = 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; = 0;
    this.x = 1;
var fast = new FastPurchase(3, 25);


"fast" objects are faster

REALITY: "SLOW" is using 15 times more memory


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());
  }, 1000);

buggyObject = null;


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");
//#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

Use appropiate scope

Unbind event listeners

Manage local cache

Better than de-referencing, use local scopes.

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

Young generation GC takes about 10ms.

Maybe it is too much time for you:

Instead of allocating and deallocating
objects, reuse them with object pools.

Note: object pools have their own drawbacks
(for example, cleaning used objects)

Three key questions

  1. Are you using
    too much memory?

  2. Do you have
    memory leaks?

  3. Is your app GCing
    too often?

Knowing your arsenal

Browser Info

You can measure how your users are using memory.

You can monitor their activity to detect unexpected use of memory
(only in Chrome) 

> performance.memory
MemoryInfo {
jsHeapSizeLimit: 793000000, usedJSHeapSize: 27600000, totalJSHeapSize: 42100000


the amount of memory that JavaScript heap is limited to

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


Memory timeline

Memory Profiling

Taking snapshots

Reading your results



distance from the GC root.
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?


Retaining memory:
the memory used by the objects
the objects they are referencing.
Use it to know where are you
using most of the memory.


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

Summary: groups by constructor name
Comparison: compares two snapshots
Containment: bird's eye view of the object structure
Dominators:  useful to find accumulation points

Understanding node colors

 Yellow :  object has a JavaScript reference on it

 Red : detached node. Referenced from one
with yellow background

You can force GC from Chrome DevTools

When taking a Heap Snapshot, it is automatically forced.
In Timeline, it can be very convenient to force a GC.

Memory leak pattern

Some nodes are not being collected:

The 3 snapshot technique


Your long running application is in an stationary state.

Memory oscillates around a constant value.

(or has a constant, controlled, expected and justified growth).

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"

The 3 snapshot technique 


Simpler & more powerful

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:

Or you can use:

Thank you!

(btw, we are hiring!)


By Gonzalo Ruiz de Villa