Finding JS Memory leaks with chrome development tools
Memory Issues
Trouble shooting memory issues in web application is very important and it needs careful understanding of memory analysis. There are various memory issue can happens to your website out of them Memory Leak is very dangerous one which makes your page cumulatively slow and finally stuck and die at certain point and hence incur business loss. Therefore, I will focus more on Memory Leak issue in this article. Chrome Development tool has done a fantastic job by giving many handy tools to learn about your website performance. In this article I will explain how to use Chrome Performance Tool to find out the memory leak in your website.
There are 3 types of memory issues found in websites and they are as following:
1- Performance is bad consistently
Symptom of Memory Bloat: Page is using more memory than its capacity.
2- Performance is delayed or appears to pause frequently
Garbage Collecting can effect performance
3- Performance gets progressively worse over time
This is a symptom of Memory Leak. Page uses more and more memory over time and the browser slows down and page stop responding.
How to find Memory Leak
Heap Allocation & Heap Snapshots in Chrome Development Tool.
What is Heap ?
It is a data-structure that holds the entire page JavaScript data in-memory.
In order find out the Memory Leak in your website you need to understand 2 things 1) Heap Allocations 2) Heap Snapshots
Heap Allocations Definitions
Heap Allocations find out when new memory is being allocated in your JavaScript heap & visualize memory usages over time.
In Chrome Development Tool Allocation sampling is the tool to record JavaScript allocation over time used to isolate the memory leaks. If you see gradual increase on stacks then you know there is memory leak going on.
Heap Snapshot definition
Heap Snapshots identify detached DOM trees this is very common cause of memory leaks. If there is a memory leak then where is the memory leak? Heap snapshot is way to show how the memory is distributed across the JavaScript objects and DOM Nodes.
If I take the first snapshot at certain time then you will see below screen:
How to Compare Heap Snapshots
Sometimes it is important to know where the memory leak is happening by comparing the various snapshots. You can take a heap snapshots at different point of times and chrome dev tool will create the snapshots. Then you can compare the available snapshots using the dev tool..
When you select compare option in dev tool then you will see below page.
Heap snapshots sections for memory leak analysis
You will discover 3 important sections in heap snapshot comparison that I personally use to track memory leak.
- New
- New things added in JavaScript or DOM
- Deleted
- How many nodes or JavaScript elements are deleted.
- Delta
- Plus number :describes in next snapshot you got extra memory allocation.
- Zero: describes all cleared
- Negative number: describes if more elements got deleted.
You should focus more on the numbers with plus sign, you need to dig more on this tree structure.
Garbage collected dom elements (GOOD)
When an HTML element is showing on the page and functional it is attached to the DOM. Therefore, garbage collector checks for these DOM elements and it will not remove them fully from the page.
When we remove some DOM element from the page by writing program then those elements are no longer with DOM and hence they are called as detached elements. These kind of detached elements are target for Garbage Collector. All detached DOM Elements are cleared from the page by Garbage Collector and their withhold memory should be reclaimed by the page.
Detached DOM Elements ( BAD )
When you have some DOM elements which are removed from the DOM however they are still connected or referenced with the JavaScript Objects or Window Object then those elements are called as DETACHED ELEMENTS.
These are the elements which are not Garbage Collected and they are still hanging around on the Page. They keep the memory allocations and when you have many
elements such that then our page starts accumulating memory. Hence our page starts having Memory Leak
Example of bad code causing detached dom elements
This is a simple website where you can add todo list. After adding couple of todo list you can select escape
button from keyboard to go to the home page.
https://js-memory-leak.stackblitz.io
Lets see what is the problem with the cod.e.
Lets see what is the problem with this code why it is creating a memory leak problem. 👮
Download Source Code Link: https://stackblitz.com/edit/js-memory-leak
import './style.css';<br> import $ from 'jquery';
$(function () { var addMoreTodo = $('<button class="btn btn-primary" id="addMoreTodo">Add More Todo...</button>'); var addTodoForm = $(` <form> <div id="addTodoForm"> <div class="form-group"> <label for="formControlRange">Todo Item</label> <input class="form-control-range" id="todotext" /> </div> <div style="padding-top:3px"> <button type="submit" class="btn btn-primary" id="addtodo" >Add Todo</button> </div> </div></form>`); $('#container').html(addTodoForm); focusTodo(); window.addEventListener('keyup', function (e) { if (e.which == 27) { $('#container').html(addMoreTodo); $('#addMoreTodo').focus(); $('#addMoreTodo').on('click', function () { $('#container').html(addTodoForm); focusTodo(); return false; }); } }); $('#addtodo').on('click', function () { const todo = $('#todotext').val(); $('#list').append(`<li class="list-group-item"> ${todo} </li>`); $('#todotext').val('').focus(); return false; }) function focusTodo() { $('#todotext').focus(); } });
window.addEventListener('keyup', function (e) { if (e.which == 27) { $('#container').html(addMoreTodo);
In the above code every time we select escape button we are adding Add More Todo
Button into the DOM and we not removing the event handlers from the previous DOM. Therefore, the old DOM is not getting cleared or claimed by Garbage collector. Since the old DOM which got replaced has still event handlers connected to Window object, garbage collector will not remove it from memory. And therefore, every time we select escape button we create one Detached Element
.
$('#addMoreTodo').on('click', function () { $('#container').html(addTodoForm); focusTodo(); return false; });
In above code also you notice when click on Add More Todo
button we are replacing the container HTM with Add Todo Form
without even properly destroying the old todo form. Therefore old todo form is still having click event handler which is connected with window object in JavaScript world. Hence Garbage collector can not clean up it and we start creating a memory allocation which never going to be clean up. The more todo we add the more memory allocations we create and our website will become sluggish and slow. Hence we created a memory leaking website.
Lets see next how to fix this issue.
Fixing detached elements and removing memory leak
We will do 2 things:
- Using jQUery elements caching. That means we will query the dom elements and cache it in a local variable so that we do not need to query every time to them.
- We will do show hide rather replacing HTML on top of each other. When we select
escape
button then it will toggle the visibility ofadd todo form
andadd more todos
button.
// FIXING MEMORY LEAK $(function () { var addMoreTodo = $('<button class="btn btn-primary" id="addMoreTodo">Add More Todo...</button>'); var addTodoForm = $(` <form> <div id="addTodoForm"> <div class="form-group"> <label for="formControlRange">Todo Item</label> <input class="form-control-range" id="todotext" /> </div> <div style="padding-top:3px"> <button type="submit" class="btn btn-primary" id="addtodo" >Add Todo</button> </div> </div></form>`); $('#container').append(addTodoForm); $('#container').append(addMoreTodo); addMoreTodo.on('click', function () { addTodoForm.toggle(); addMoreTodo.toggle(); focusTodo(); return false; }); var list = $('#list'); var todoText = $('#todotext'); addTodoForm.find('#addtodo').on('click', function () { const todo = $('#todotext').val(); list.append(`<li class="list-group-item"> ${todo} </li>`); todoText.val('').focus(); return false; }) addTodoForm.hide(); window.addEventListener('keyup', function (e) { if (e.which == 27) { addMoreTodo.toggle().focus(); addTodoForm.toggle(); } }); function focusTodo() { $('#todotext').focus(); } });