Background
When you close the browser tab, all the memory is released. Memory leaks are most likely not big problems on web browsers. But if you have a long running application, it can be a problem. This is a note on how to detect memory leaks on web browsers. In this note, I will use examples to show how to use the memory capability on the development tools. I noticed that the development tools on Chrome and Edge are the most easy to use.
Take a Heap Snapshot
In Chrome or Edge development tools, we can take three kinds of memory profiles.
![Image 1](/KB/Articles/5306829/profile-types.PNG)
In this note, I will be focusing on the "Heap snapshot", it is very effective to pinpoint exactly which object is causing the memory leakage. The test HTML page is the following:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<style>
button {
height: 60px;
border-radius: 5px;
font-weight: 800;
background-color: aquamarine;
}
</style>
<title>Memory test - first attempt</title>
<script>
let MTestClassesArray = [];
class MTestClass {
constructor() {}
}
const create_1000_objects = () => {
for(let i = 0; i < 1000; i++) {
MTestClassesArray.push(new MTestClass());
}
const text = `Total ${MTestClassesArray.length} `
+ `MTestClass objects created`;
document.getElementById('divMsg').innerHTML = text;
};
</script>
</head>
<body>
<button onclick="create_1000_objects()">
Create 1000 MTestClass objects
</button>
<div id="divMsg" />
</body>
</html>
- In this HTML page, we have a button.
- Every click on the button, there are 1000 objects of type "
MTestClass
" created. - Each object is inserted into the array "
MTestClassesArray
". - Since the array "
MTestClassesArray
" is declared in the global scope, the objects are no longer garbage collectable.
To verify the above statements, let us load the page and click on the button a few times. In my experiment, I used Edge.
![Image 2](data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==)
As it is indicated in the page, I have clicked the button 3 times and 3000 "MTestClass
" objects have been created. We can bring up the development tool by "CTRL+SHIFT+i". Before taking the memory snapshot, let us first force a garbage collection.
![Image 3](data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==)
We can then take a memory snapshot after the garbage collection.
![Image 4](data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==)
The memory snapshot gives us great visibility to the memory heap.
- We can filter the snapshot to check if objects of certain class type are in the memory heap. In our example, we can see exactly 3000 "
MTestClass
" objects. - We can also see what object is preventing the "
MTestClass
" objects being garbage collected. In our example, it is the "MTestClassesArray
" object.
The development tool can even tell us which line of code created the "MTestClass
" objects.
![Image 5](data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==)
A Real-World Example - AsyncSubject
If you subscribe to an event, you need to un-subscribe it. If not, it will be very likely be a memory leak. But is it the case for the "AsyncSubject
" in rxjs?
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<style>
button {
height: 60px;
border-radius: 5px;
font-weight: 800;
background-color: aquamarine;
}
</style>
<title>Memory test - async subject</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/7.1.0/rxjs.umd.js"></script>
<script>
const get_time = () => {
const element = document.getElementById('divTime');
const subject = new rxjs.AsyncSubject();
element.innerHTML = '';
setTimeout(() => {
subject.next(new Date());
subject.complete();
}, 3 * 1000);
subject.subscribe((data) => {
element.innerHTML = data.toString();
});
};
</script>
</head>
<body>
<button onclick="get_time()">Get Time by AsyncSubject</button>
<div id="divTime"></div>
</body>
</html>
- In this HTML page, we have a button.
- Clicking on the button, the current time will be displayed on the page.
- Instead of getting the time directly, I created an "
AsyncSubject
" and the time is obtained in a timer callback function. - The program then "subscribe" to the "
AsyncSubject
" and display it on the page.
It should be noted that the "AsyncSubject
" is created in the "get_time
" arrow function, so it is not associated to any global context. I will assume that it is garbage collectable after the timer callback. But let us confirm it is using the development tool.
![Image 6](data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==)
There is a 3 second delay between the button click and when the time is displayed. In order to confirm that the "AsyncSubject
" is garbage collectable, let us take two heap snapshots. One before the timer callback and one after the timer callback.
![Image 7](data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==)
Before the timer callback, we can find the "AsyncSubject
" in the memory heap. At this time, it is not garbage collectable because it is retained by some other resources and eventually reachable from the window object.
![Image 8](data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==)
But after the timer callback, the "AsyncSubject
" is collected. If it is not collected, you can manually force a collection. With this example, we can have better confidence on our program. If the "AsyncSubject
" is used correctly, we do not need to un-subscribe the event.
Additional Note
- It has been noted that if you ever "
console.log()
" an object, it is no longer garbage collectable. In a production release, you should keep it in mind, if you have memory problems. - When searching for objects in the heap snapshot, the name of the class can be changed by a minifier, if your program is minified.
Points of Interest
- This is a note on how to detect memory leaks on web browsers.
- The development tool can be very useful for us to have some visibility to the memory heap.
- I hope you like my postings and I hope this note can help you one way or the other.
History
- 30th June, 2021: First revision
I have been working in the IT industry for some time. It is still exciting and I am still learning. I am a happy and honest person, and I want to be your friend.