Memory Leaks

in Javascript

Detect. 

Correct.

Prevent

Intro

Attitude towards the problem

What is a memory leak?

Most of those who don't work with memory management

don't know what it is

What about memory leaks

in your javascript code?

  • There's no problem
  • There's no solution
  • Let somebody else
  • I have been solving
  • I can avoid the problem

Developer stages

Part 1

Memory management

Memory life cycle

Allocate

Use

Release

Regardless of the programming language, memory life cycle is pretty much always the same

Memory allocation

Value initialization (when you define literals)


var n = 123; // allocates memory for a number
var s = 'azerty'; // allocates memory for a string 

var o = {
  a: 1,
  b: null
}; // allocates memory for an object and contained values

// (like object) allocates memory for the array and 
// contained values
var a = [1, null, 'abra']; 

function f(a) {
  return a + 2;
} // allocates a function (which is a callable object)

// function expressions also allocate an object
someElement.addEventListener('click', function() {
  someElement.style.backgroundColor = 'blue';
}, false);

Memory allocation

Allocation via function calls (hidden memory allocation)


var d = new Date(); // allocates a Date object

var e = document.createElement('div'); // allocates a DOM element

var s = 'azerty';
var s2 = s.substr(0, 3); // s2 is a new string
// Since strings are immutable value, 
// JavaScript may decide to not allocate memory, 
// but just store the [0, 3] range.

var a = ['ouais ouais', 'nan nan'];
var a2 = ['generation', 'nan nan'];
var a3 = a.concat(a2); 
// new array with 4 elements being
// the concatenation of a and a2 elements

Memory can be represented by a directed Object Graph 

A value can have one or more retaining paths

Object Graph

Root

Object value

Scalar value

Use allocated memory

Using values basically means reading and writing in allocated memory. This can be done by reading or writing the value of a variable or an object property or even passing an argument to a function.

Memory size after allocation or modification

depends on how Javascript Engine works.

For example there are slow and fast objects in V8

Release allocated memory

when the memory is not needed anymore

Javascript use garbage collector

There's no way to manual memory release because

that releases memory we don't need

Garbage Collection

in Javascript

Part 2

What is garbage?

It depends on the garbage collector algorithm

Garbage collector

algorithms

  • Reference Counting
  • Mark-Sweep Collector
  • Mark-Compact Collector
  • Copying Collector
  • Generational Collector

But what about Javascript Engines and Browsers?

Browsers Garbage Collectors

Javascript Engine Browsers Garbage Collector
SpiderMonkey Mozilla Firefox The GC is a mark-and-sweep, non-conservative (exact) collector.
Webkit Safari Wavefront Concurrent Garbage Collector. it's Guy Steele’s classic retreating wavefront write barrier with a mature sticky-mark-sweep collector
ChakraCore Microsoft Edge Mark-and-sweep garbage collector that supports concurrent and partial collections. 
V8 Chromium, Chrome, Opera, Vivaldi ​Stop-the-world, generational, accurate, garbage collector.

Reference-counting garbage collection

Advantages

  • Garbage collection can be immediate

Disavantages

  • Cannot handle cyclic references 
  • Need extra memory for the reference counter
  • Incrementing and decrementing reference counts every time a reference is created or destroyed can significantly impede performance.

Algorithm actual for IE 6 and IE 7 (but IE is not actual now)

Reference-counting garbage collection

Let's investigate the code below

// window is a root object in the browser javascript

window.a1 = { a2: {} }

// create circular reference
window.a1.a2.parentReference = window.a1 

window.b1 = { b2: 2017 }
window.c1 = { c2: {} }

window.a1 = null // remove a1 object reference
window.c1.c2 = null // remove c2 object reference

Investigate code example

Root

Object value

Scalar value

A

B

C

Represent the code as object graph with reference counters

window

A1

A2

B2

B1

C1

C2

1

1

1

1

2

1

Investigate code example

Root

Object value

Scalar value

A

B

C

Remove references

window

A1

A2

B2

B1

C1

C2

1

1

1

1

1

0

Investigate code example

Root

Object value

Scalar value

A

B

C

Delete objects without references

window

A1

A2

B2

B1

C1

C2

0

1

1

1

1

1

Investigate code example

Root

Object value

Scalar value

A

B

C

Result of reference-counting garbage collector working

window

A1

A2

B2

B1

C1

  • As entire heap is scanned, pauses would be longer
  • If heap is paged can have performance issues
  • Causes heap fragmentation which could lead of out of memory errors

Mark-and-sweep algorithm

As of 2012, all modern browsers ship a mark-and-sweep garbage-collector.

All improvements made in the field of JavaScript garbage collection over the last few years are implementation improvements of this algorithm

Advantages

  • Can handle cyclic references
  • No burden on the compiler or the application

Disadvantages

Mark-and-sweep algorithm

Let's investigate the code below

// window is a root object in the browser javascript

window.a1 = { a2: {} }

// create circular reference
window.a1.a2.parentReference = window.a1 

window.b1 = { b2: 2017 }
window.c1 = { c2: {} }

window.a1 = null // remove a1 object reference
window.c1.c2 = null // remove c2 object reference

Investigate code example

Root

Object value

Scalar value

A

B

C

Represent the code as object graph

window

A1

A2

B2

B1

C1

C2

Investigate code example

Root

Object value

Scalar value

A

B

C

Remove references

window

A1

A2

B2

B1

C1

C2

Investigate code example

Root

Object value

Scalar value

A

B

C

Mark garbage collector phase

window

A1

A2

B2

B1

C1

C2

* Objects are allocated into JS Heap. For example there're Heap and HeapObject in V8 Engine

Investigate code example

Root

Object value

Scalar value

A

B

C

Sweep garbage collector phase

window

B2

B1

C1

Investigate code example

Root

Object value

Scalar value

A

B

C

Result of mark-and-sweep garbage collector working

window

B2

B1

C1

What will be garbage

dependent on the garbage collector algorithm

Garbage collector

is non-deterministic

It means one cannot be certain when a collection will be performed.

Most GC implementations share the common pattern of doing collection passes during allocation.

Nonetheless

what is a memory leak?

Part 3

Memory leak

Occurs when program acquires memory but fails to release it back to the operating system.

Memory leak

It looks like a woman's wardrobe

The woman is a program

The dress is allocated memory

The size wardrobe is limited allowed memory

More and more dresses, only a few used

Not used dresses are memory leaks when

Why memory leak is bad

More memory for better performance

Types of Memory leaks

Static

Dynamic

Page's memory consumption will continue growing

Memory is allocated only once (one-time leak)

based on memory growing characteristic 

Part 4

Memory leak detection

Memory leak

is not only memory you don't release,

but also memory you don't need anymore

Graph Objects

is not only memory you don't release,

but also memory you don't need anymore

Performance profile

to detect memory leak presence 

Is there a memory leak? No thanks!

The sawtooth curve

There are a lot of allocations

of shortly lived objects

Do you want an example?


setInterval(function(){ 
  console.log("Remember! There's no memory leak") 
}, 10);

Code is not as scary as performance profile

Is there a memory leak? Perhaps!

The staircase

If the memory heap grows after GC was called so there's a memory leak

Is there a memory leak? Definitely!

The slope sawtooth curve

GC tries to release allocated memory

V8 Garbage Collector 

Be sure that GC minor and GC major were called both

3 Snapshot Technique

to detect where is a memory leak

* First used by Loreena Lee in the Gmail team

  • Take a heap snapshot.
  • Do stuff.
  • Take another heap snapshot.
  • Repeat the same stuff.
  • Take another heap snapshot.
  • Filter objects allocated between Snapshots 1 and 2 in Snapshot 3's "Summary" view.

3 Snapshot Technique

useless for static memory leaks

¯\_(ツ)_/¯

Let's practice

There's an one button UI

Magical Example

User can push the button only

Performance profile

There's the staircase pattern! Memory leak!?

3 snapshot technique

There are 100 new HTML Elements and Text Nodes

Part 5

Antipatterns and fixes

  • Circular references
  • Global variables
  • Callbacks
  • Not killed timers
  • DOM references
  • Closures
  • Browser implementation specifics

Types of Event Leaks

patterns are based on source of problem

# Circular references

Old Internet Explorer Example


var foo = document.getElementById("foo")
var bar = document.getElementById("bar")

foo.element = bar
bar.element = foo

foo.parentNode.removeChild(foo)
bar.parentNode.removeChild(bar)

foo = null
bar = null

var foo = document.getElementById("foo")
var bar = document.getElementById("bar")

foo.element = bar
bar.element = foo

foo.parentNode.removeChild(foo)
bar.parentNode.removeChild(bar)

foo.element = null
bar.element = null

foo = null
bar = null

How to Fix

Example of Code

# Global variables

Cache Service


(function(window) {
  cache = []

  window.CacheService = function() {
    return {
      cache: function(value) {
        cache.push(new Array(1000).join('*'))
        cache.push(value)
      }
    }
  }
})(window)

var service = new window.CacheService()

for (let i=0; i < 99999; i++) {
  service.cache(i)
}

service = null

Example of Code 

Cache Service

Performance profile

* GC was manually called between 7 and 9 seconds

Cache Service Example


(function(window) {
  window.CacheService = function() {
    var cache = []

    return {
      cache: function(value) {
        cache.push(new Array(1000).join('*'))
        cache.push(value)
      }
    }
  }
})(window)

var service = new window.CacheService()

for (let i=0; i < 99999; i++) {
  service.cache(i)
}

service = null

How to fix

Performance profile

Fixed Cache Service Example

* GC was manually called between 6 and 7 seconds

# Callbacks


function checkStatus() {
  fetch('/endpoint').then(function(response) {
    var container = document.getElementById("container"); 
    container.innerHTML = response.status;

    container.addEventListener("mouseenter", function mouseenter() { 
      container.innerHTML = response.statusText;
    });
    container.addEventListener("mouseout", function mouseout() { 
      container.innerHTML = response.status;
    });
  })
}

setInterval(checkStatus, 100)

Check Status Example


<div id="container" />

Fetch available status of endpoint

Check Status Example

Performance profile

Extract the problem

Callbacks for event listener are created each 100ms


var container = document.getElementById("container"); 

setInterval(function() {
  container.addEventListener("mouseout", function mouseout() { 
    // some code here
  });
}, 100)

Extract the problem code

Performance profile


var container = document.getElementById("container");
var status = {
  code: null,
  text: null
}

container.addEventListener("mouseenter", function() { 
  container.innerHTML = status.code;
});

container.addEventListener("mouseout", function() { 
  container.innerHTML = status.text;
});

function processResponse(response) {
   status.code = response.status
   status.text = response.statusText
}

function checkStatus() {
  fetch('/endpoint').then(processResponse)
}

setInterval(checkStatus, 100)

Check Status Example

How to fix

Fixed Check Status Example

Performance profile


function(scope, element, attrs) {  
  element.on('click', function() {
    scope.selected = true
  })
}

Angular Example

Poorly Managed Event Handlers in directives


function(scope, element, attrs) {  
  element.on('click', function() {
    scope.selected = true
  })
  scope.$on('$destroy', function() {
    element.off() // deregister all event handlers
  })
}

How to Fix


$scope.$on('someEvent', function() {
  $scope.refresh()
}

Angular Example

Poorly Managed Watchers


var cleanup = $scope.$on('someEvent', function() {  
  $scope.refresh()
})
$scope.$on('$destroy', function() {
  cleanup()
})

How to Fix

# Not killed timers


var strangeObject = { 
  callAgain: function () {
    var ref = this 
    var val = setTimeout(function () {
      ref.callAgain() 
    }, 50) 
  } 
} 

strangeObject.callAgain() 
strangeObject = null

Gonzalo Ruiz de Villa Example

Performance profile of code

Gonzalo Ruiz de Villa Example

It there a memory leak?


var strangeObject = {
  storage: [], 
  callAgain: function () {
    var ref = this 
    ref.storage.push(new Array(1000000).join('*'))
    var val = setTimeout(function () {
      ref.callAgain() 
    }, 50) 
  } 
} 

strangeObject.callAgain() 
strangeObject = null

Add special property to store values in strangeObject

Gonzalo Ruiz de Villa Modified Example 

Performance profile of code

Gonzalo Ruiz de Villa Modified Example 

How to fix

Gonzalo Ruiz de Villa Modified Example 


var strangeObject = {
  storage: [], 
  startCallAgain: function() {
    this.interval = setInterval(this.tickCallAgain.bind(this), 50) 
  },
  tickCallAgain: function() {
    this.storage.push(new Array(1000000).join('*'))
  },
  stopCallAgain: function() {
    if (this.interval) {
      clearInterval(this.interval)
    }
  }
} 

strangeObject.startCallAgain() 

setTimeout(function() {
  strangeObject.stopCallAgain()
  strangeObject = null
}, 5000)

Performance profile

Fixed Gonzalo Ruiz de Villa Modified Example 


class IntervalComponent extends React.Component {
  // ..
  handleInterval() {
    console.log('boom', this.props.id)
  }

  componentDidMount() {
    setInterval(this.handleInterval, 1000);
  }
  // ..
}

React Component Example


class IntervalComponent extends React.Component {
  // ..
  handleInterval() {
    console.log('boom', this.props.id)
  }

  componentDidMount() {
    this.interval = setInterval(this.handleInterval, 1000)
  }

  componentWillUnmount() {
    clearInterval(this.interval)
  }
  // ..
}

How to Fix

# DOM Refences


var elements = {
  container: document.querySelector('#container'),
  form: document.querySelector('form'),
  submit: document.querySelector('[type="submit"]')
};

elements.form.addEventListener('submit', function(e) {
  e.preventDefault();
  elements.container.innerHTML = 'Ops..';
})

Static memory leak

Synthetic example

3 snapshot technique useless here


<div id="container">
  <form>
    <input type="submit" value="Push me" />
  </form>
</div>
var elements = {
  container: document.querySelector('#container'),
  form: document.querySelector('form'),
  submit: document.querySelector('[type="submit"]')
}

function processSubmit(e) {
  e.preventDefault()

  elements.form.removeEventListener('submit', processSubmit)
  elements.container.innerHTML = 'Ops..'
  
  elements = {
    container: document.querySelector('#container')
  }
}

elements.form.addEventListener('submit', processSubmit)

Synthetic example

How to fix

# Closures


function f() {
  var some = [];
  while(some.length < 1e6) {
    some.push(some.length);
  }
  function unused() { some; } //causes massive memory leak
  return function() {};
}

var a = [];
var interval = setInterval(function() {
  var len = a.push(f());
  document.getElementById('count').innerHTML = len.toString();
  if (len >= 500) {
    clearInterval(interval);
  }
}, 10);

Unused Code Example

Unused Code Example

Performance profile of code

Memory can't  be released because it can't be allocated before


function f() {
  return function() {};
}

var a = [];
var interval = setInterval(function() {
  var len = a.push(f());
  document.getElementById('count').innerHTML = len.toString();
  if (len >= 500) {
    clearInterval(interval);
  }
}, 10);

Unused Code Example

How to Fix? Investigate and refactor the code


var theThing = null
var replaceThing = function () {
  var originalThing = theThing
  var unused = function () {
    if (originalThing)
      console.log("hi")
  }
  theThing = {
    longStr: new Array(1000000).join('*'),
    someMethod: function () {
      console.log(someMessage)
    }
  }
}
setInterval(replaceThing, 1000)

MeteorJS Example

JavaScript memory leak in Meteor’s live HTML template rendering system

MeteorJS Example

Performance profile


var theThing = null
var replaceThing = function () {
  var originalThing = theThing
  var unused = function () {
    if (originalThing)
      console.log("hi")
  }
  theThing = {
    longStr: new Array(1000000).join('*'),
    someMethod: function () {
      console.log(someMessage)
    }
  }
  originalThing = null;
}
setInterval(replaceThing, 1000)

MeteorJS Example

How to Fix? Just null the originalThing object

Fixed MeteorJS Example

Performance profile

# Browser issues

Check browser's

Bugtracker


setInterval(function(){
  var xhr = new XMLHttpRequest();
  xhr.open('GET', 'json.txt', true);
  xhr.onreadystatechange = function() {
    if(this.readyState == 4 && this.status == 200) {
      console.log(this.responseText);
    }
  };
  xhr.send('');
}, 500);

Chromium AJAX Memory Leak (Fixed)


var patt1 = new RegExp(
  "((.*)(((((((((((((((((((((((((((((((.*(.*(.*(.*" + 
  "(.*(.*(.*(.*(.*(.*(.*(.*(.*(.*(.*(.*(.*(.*(.*(.*" + 
  "(.*(.*(.*(.*(.*(.*(.*(.*(.*(.*(.*){10}(.*){10}" + 
  "(.*){10}(.*){10}(.*){10}(.*){10}(.*){10}(.*){10}" + 
  "(.*){10}(.*){10}(.*){10}(.*){10}(.*){10}(.*){10}" + 
  "(.*){10}(.*){10}(.*){10}(.*){10}(.*){10}(.*){10}" + 
  "(.*){10}(.*){10}(.*){10}(.*){10}(.*){10}(.*){10}" +
  "(.*){10}(.*){10}(.*){10}(.*){10}.*)+).*)+).*)+).*" +
  ")+).*)+).*)+).*)+).*)+).*)+).*)+).*)+).*)+).*)+).*" + 
  ")+).*)+).*)+).*)+).*)+).*)+).*)+).*)+).*)+).*)+).*" + 
  ")+).*)+).*)+).*)+).*)+).*)+).*)+))"
)

document.write(patt1.exec("peace"))

Mozilla Firefox RegExp Memory Leak (Not Fixed)

Part 6

Prevent memory leaks

Design

Code

Profile

Then

Then

Good rules to follow

  • Avoid circular object references
  • Avoid long-lasting refs to DOM elements you no longer need
  • 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.
  • Manage local cache. Be careful with storing large chunks of data that you are not going to use. 

ES6's weak.. what?

References to objects in the collection and maps are held weakly

WeakSets and WeakMaps are not enumerable


const weakMap = new WeakMap()
let fruits = ['apple', 'orange', 'banana']

weakMap.set(fruits, 'value')

console.log(weakMap.has(fruits)) // output: true

fruits = null

console.log(weakMap.has(fruits)) // output: false

Afterword

You are not alone

By the way 

Angular Issues

Facebook React Issues

Resources

Any questions?

Memory leaks in Javascript

By Ufocoder

Memory leaks in Javascript

Detect. Correct. Prevent

  • 197
Loading comments...

More from Ufocoder