Investigating Memory Leaks

and performance tuning your Javascript

@abinavseelan

blog.campvanilla.com

What's in store

  • What exactly is memory? 🙃
  • Garbage Collection 🚚
  • What is a memory leak? 🤔
  • Common causes. 💀
  • Investigating memory leaks! 🔍

Memory?

Allocate the memory

Use the allocated memory

Release the memory



// Allocation

let a = 10; // allocates memory to hold the value 10

const obj = { // Allocates memory for obj and extends that memory for each key
    firstName: 'foo',
    lastName: 'bar'
};

const arr = [1, 2, 3]; // Allocates memory for arr

const now = new Date(); // Allocation via function call


// Use

a = 20; // Modify the allocated memory

obj.birthday = new Date(); // Extend the allocated memory

Allocate the memory

Use the allocated memory

Release the memory

Garbage Collector

The

A garbage collector is a mechanism that helps reclaim unused memory 

Reference Counts helps indicate when memory is unused



function someFunc() {

} 


function someFunc() {

    const someConst = 10;

    // While inside the function someFunc,
    // someConst has a reference to it
    // hence the memory allocated to it
    // will not be reclaimed
}

// Outside someFunc, someConst is not referenced
// and the Garbage collector cleans it up
const someConst = 10;

const obj = {
    val: someConst // the constant someConst has a reference count of 1
}


const newObj = obj; // the object obj has a reference count of 1

newObj.newVal = someConst; // someConst has a reference count of 2


obj.val = 10;

// Here, someConst loses one reference count
// someConst has a reference count of 1


newObj = {
    foo: 'bar'
}

// Here, both someConst and obj lose one reference.
// obj has a reference count of 0
// someConst has a reference count of 0
// Since both have a reference count of 0, they will be garbage collected!

However...

// Example from MDN

function someFunc() {
    const obj = {};
    const newObj = {};

    // Create a cyclic reference
    obj.val = newObj;
    newObj.val = obj;
}

// Even outside the function someFunc
// obj and newObj have 1 reference count
// and the garbage collector will not
// clean this up.

Reference Counts Unreachability helps indicate when memory is unused

Mark & Sweep Algorithm

Unreachable Code

Unreachable Code

Memory Leaks

A memory leak occurs when you don't need an object, but the runtime thinks you do and you're unintentionally using memory

But...why should you care?

A pretty common symptom of a memory leak is that a page's performance gets progressively worse over time.

Jank

Common Causes

  • Accidental Global Variables
  • Forgotten Timers
  • Closures
  • Detached DOM trees
  • Retaining tree 

Common Causes

  • Accidental Global Variables
  • Forgotten Timers
  • Closures
  • Detached DOM trees
  • Retaining tree
function someFunc() {
    b = 10; //looks pretty harmless right?
}
function someFunc() {
    b = 10;
}

// is the same as

function someFunc() {
    window.b = 10; // Oops!

    // This happens due to scope lookup
    // If the variable does not exist in any scope
    // the JS runtime creates the variable
    // in the GLOBAL scope! 😱
}

// The variable is never cleaned

Common Causes

  • Accidental Global Variables
  • Forgotten Timers
  • Closures
  • Detached DOM trees
  • Retaining tree
setInterval(() => {
    const someConst = 10;

    // do something here    
}, 1000);

// someConst may be cleaned at some point

// The handler itself is still referenced
// and will not be cleaned unless ... 
// The fix? Assign the reference to a variable

let interval = setInterval(() => {
    const someConst = 10;

    // do something here    
}, 1000);


// some code


// When you're done with the interval
clearInterval(interval);

Common Causes

  • Accidental Global Variables
  • Forgotten Timers
  • Closures
  • Detached DOM trees
  • Retaining tree
// A trivial example


function someFunc() {
    const a = 10; // 4 bytes, let's say

    return function() {
        console.log(a);
    }
}

const arrayOfFunction = [];


for (let i = 0; i < 1000000, i++) {
    arrayOfFunction[i] = someFunc();        
}
// A real-world example

const inputField = document.getElementById('input-field');


function someFunc() {
    // do something with inputField
}

// inputField is not longer referenced anywhere
// it can be cleaned.
// A real-world example

const inputField = document.getElementById('input-field');

const button = document.getElementById('cta');

button.addEventListener('click', () => {
    // do something with inputField
});

// Here, it will not be cleaned since the runtime
// cannot decide when the memory allocated to 
// inputField is not longer required

The runtime is smart though

function someFunc() {
    const a = 10;
    const b = 20;
    
    this.sendAlert = function() {
        alert(a);
    }
}

const btn = document.getElementById('cta');

btn.addEventListener('click', () => {
    new someFunc().sendAlert();

    // Only the memory allocated for `a` will not be cleaned.
});

Common Causes

  • Accidental Global Variables
  • Forgotten Timers
  • Closures
  • Detached DOM trees 💀
  • Retaining tree

A detached DOM tree is a DOM tree that is not part of the actual DOM tree, but is referenced by Javascript

Javascript

DOM

Javascript

DOM

DOM Node

Javascript

DOM

DOM Node

Reference

Javascript

DOM

DOM Node

Reference


let inputContainer = document.getElementById('input-field-1');

let button = document.getElementById('cta');

let inputBox = inputContainer.querySelect('input');

button.addEventListener('click', () => {
    inputContainer.remove();
    // removes the DOM node from the DOM tree
});

// But, it is still referenced in JS by inputBox
// This is a detached DOM tree.

Common Causes

  • Accidental Global Variables
  • Forgotten Timers
  • Closures
  • Detached DOM trees
  • Retaining Tree

const obj = {
    numVal: 100, // 4 bytes
    strVal: 'Hello World' // 11 bytes
}

const btn = document.getElementById('cta');

btn.addEventListener('click', () => {
    alert(obj.numVal);
    // This will hold on to 15 bytes
    // not 4 bytes
});

Chrome Dev Tools

Investigating leaks using

performance.memory

Chrome's

Task Manager

The

Memory Tab

The

Demo

https://goo.gl/2AzHX9

Build first, optimise after

A recap

Memory goes through a memory life-cycle

Garbage collectors use a mark-and-sweep algorithm to reclaim memory

Memory leaks are caused when the garbage collector and you as the programmer are basically ... not in sync. 

Memory leaks cannot be found by a compiler/linter. You should take extra care to avoid them. :) 

A recap

The most common causes for memory leaks are 

  • Accidental global variables
  • Forgotten Timers
  • Closures
  • Detached DOM trees
  • Retaining Trees

 

A recap

To investigate a memory leak, first fire up the task manager to see the memory footprint

Use the dev tools to then narrow down the type of memory leak that is happening! :)

That's all folks! 🚀

@abinavseelan

blog.campvanilla.com

Investigating Memory Leaks (and performance tuning your Javascript)

By Abinav Seelan

Investigating Memory Leaks (and performance tuning your Javascript)

Talk given at 1) Flipkart's UI Bootcamp, 2018 2) AngularJs & ReactJS Meetup on 21st Oct, 2017

  • 2,043