Hunting memory leaks in JavaScript using Chrome DevTools
Have you ever left a web app opened in a tab for a couple of hours to come back and see this screen? In this post we will learn how to use the Chrome DevTools to find memory leaks.
Memory management
JavaScript is a garbage collected language, but we should still learn about memory management, as making memory leaks in your code is quite easy. In short the garbage collector tracks memory allocation and when a piece of allocated memory is not needed any longer, it frees it. Unreachable pieces of memory are considered garbage, but for the rest only the developer can decide whether it’s needed or not. So, the main causes of memory leaks in JavaScript are unwanted references like accidental global variables, forgotten intervals, detached DOM elements and others.
Visualize using Timeline
Can you spot a problem with the following code snippet?
class Task { ... render() { this.element = document.createElement('li'); ... this.deleteButton = document.createElement('button'); this.deleteButton.onclick = this.destroy.bind(this); this.element.append(this.deleteButton); return this.element; } destroy() { this.element.remove(); } } class List { ... add(title) { const task = new Task(title); this.element.append(task.render()); this.tasks.push(task); } }
Don’t worry if you can’t figure it out, the issue is not so obvious. To diagnose if an application has a memory leak, you can use the Performance section in Chrome DevTools. In the above example we want to check if continuous usage of the application (adding/removing tasks) causes unexpected increase of memory usage. To test this we can record the memory usage while adding and removing a task several times. We expect that when we add a task the memory usage increases and when we remove it – the memory usage drops back to the previous level.
- Â Start a recording
- Add a task
- Remove the newly added task
- Click on “Collect garbage”
- Go back to 2.
- When you perform this a few times stop the recording and look for patterns that grow as shown in the screenshot:
As seen on the screenshot above the nodes and listeners progressively increase and never drop while the expected outcome is a graph similar to the JS Heap line above.
Note that garbage collection is non-deterministic. In other words, it is not possible to be certain when a collection will be performed, so Chrome DevTools gives us an option to force it (step 4).
Discover detached DOM using Heap Snapshots
When it comes to finding where the problem originates the Memory section in Chrome DevTools comes in handy. With a couple of snapshots we can find which parts of the application use more memory than expected and whether we have any detached DOM elements:
- Reload the page.
- Take a heap snapshot
- Add a few tasks and remove them
- Take another heap snapshot
- Compare the two snapshots and you’ll see the second one uses more memory although we have removed all tasks.
- Expand the “Detached DOM tree” list and you will see there are still references to the DOM elements that were removed (marked in red).
The overall memory usage is proportional to the number of tasks we have and removing tasks actually drops the memory usage.
Conclusion:
Memory leaks are common in JavaScript. Some of them may cause serious performance issues, so it’s a good idea to get familiar with memory management and start profiling at an early stage, especially when working on big applications. In this article we’ve only scratched the surface, but it’s still a good start.
Resources
- Fix Memory Problems
- 4 Types of Memory Leaks in JavaScript and How to Get Rid Of Them
- How JavaScript works: memory management + how to handle 4 common memory leaks
Here you can get the fully working examples: