Memory Leaks
in Javascript
Total Recall
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
Object Graph
Memory can be represented by a directed Object Graph
var hero = { name: 'John' }
var robot = { answer: 42, root: true }
var serial = 1100
Root
Object value
Scalar value
'John'
{ name: 'John' }
{ answer: 42, root: true}
true
42
1100
Object Graph
window
A value can have one or more retaining paths
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?
* Generational Garbage Collection in Firefox
** Webkit. Introducing Riptide. Jan 20, 2017
*** ChakraCore. Architecture Overview. Garbage Collector. May 3, 2017
**** V8. Design Elements. Efficient Garbage Collection. July 17, 2017
Browsers Garbage Collectors
Javascript Engine | Browsers | Garbage Collector |
---|---|---|
SpiderMonkey | Mozilla Firefox | Generational garbage collection (GGC) has now been enabled in the SpiderMonkey JavaScript engine in Firefox 32 * |
JavaScriptCore | 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
V8: JS Heap, DOM Nodes, Event Listeners
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
Performance profile
There's the staircase pattern! Memory leak!?
3 snapshot technique
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 Memory 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 References
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 DOM 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 DOM 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
WeakMaps* and WeakSets** 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*
It's a possible problem of application growing up
Memory leak
Resources
- IBM. Memory leak patterns in JavaScript.
- MDN. JavaScript. Memory Management.
- Effectively Managing Memory at Gmail scale
- The Breakpoint Ep. 8: Memory Profiling with Chrome DevTools
- Garbage collection algorithms
- Memory Management in Chrome's JavaScript Runtime (V8) at #code16,
- Finding and debugging memory leaks in JavaScript with Chrome DevTools
- 4 Types of Memory Leaks in JavaScript and How to Get Rid Of Them
- Work with V8 memory leaks. slides, video
- Fixing Memory Leaks in AngularJS and other JavaScript Applications
- Garbage collection in V8, an illustrated guide
- "Работа с утечками в V8", Роман Кривцов, MoscowJS 19
- Debugging memory leaks: When the famous 3 snapshot technique can cost you days of development
- BloatBusters: Eliminating memory leaks in Gmail
Any questions?
Memory leaks in Javascript
By Ufocoder
Memory leaks in Javascript
Total Recall
- 3,613