How to diagnose memory issues in Chrome
Monitoring memory usage of web apps is not only useful during development but it can also assist in troubleshooting production bugs. If you want to learn how memory is managed in JavaScript and Chrome read Memory Management in JavaScript Part-1, Memory Management in JavaScript Part-2 and Memory Management in JavaScript Part-3.
Signs of Memory Issues
There are certain signs that can help developers to diagnose memory related issues in web pages.
When a webpage has a memory leak its performance worsens over time. For instance in a todo list application adding a todo item each time will make the webpage slower until it becomes unusable.
When a webpage has memory bloat its performance is consistently bad. Considering todo app example if memory usage causes RAIL performance to be consistently bad for target use case and device then there is memory bloat.
In case the webpage is pausing frequently or UI is delayed this indicates that there is frequent garbage collection. Because garbage collection will block main thread activity like rendering and script execution to name a few.
Methods for Monitoring Memory
Allocation Timelines: These can be used for monitoring the JS heap memory.
Task Manager: This can be used for monitoring JS heap and native (DOM) memory.
Heap Snapshots: This can be used for analyzing how memory is distributed among JS objects and DOM nodes at the point in time when snapshot is taken.
Performance Monitor: It can be used for monitoring JS heap and DOM nodes.
Performance Monitor
To view performance monitor open 'Chrome Menu'->'More Tools'->'Developer Tools'->'Performance'. Make sure 'JS Heap' and 'Nodes' checkboxes are the only ones checked.
<button id="add-items">Add items</button> <script> var arr = []; function grow() { //Add 10000 element Nodes to HTML DOM for (var i = 0; i < 10000; i++) { document.body.appendChild(document.createElement('p')); } //increase size of arr variable for (var i =0; i<1000; i++){ arr[arr.length+i] = i; } } document.getElementById('add-items').addEventListener('click', grow); </script>
The above code sample can be placed inside 'body' tags. Whenever the 'Add items' button is clicked it will add 10000 DOM nodes of 'p' element and also increase the size of 'arr' variable by 1000. Notice that red trend line ('Nodes') and purple trend line ('JS Heap') are increasing over many events. This indicates that there is memory leak because the trendlines are always going up without garbage collection. So either 'Nodes' or 'JS Heap' increasing trendline indicates memory leak. But if either 'Nodes' or 'JS Heap' show frequently rising and falling graphs then there is frequent garbage collection issue.
Heap Snapshot
<button id="add-items">Add items</button> <script> var arr = []; var domNodes; function grow() { var divElem = document.createElement('div'); for (var i =0; i<10000; i++){ var pElem = document.createElement('p'); divElem.appendChild(pElem); var obj = {i:i}; arr.push(obj); } domNodes = divElem; } document.getElementById('add-items').addEventListener('click', grow); </script>
To take a heap snapshot open 'Chrome Menu'->'More Tools'->'Developer Tools'->'Memory'->'Heap Snapshot'->'Take Snapshot'. In this case two heap snapshots are taken, 'Snapshot1' has been taken after forcing garbage collection so memory is freed. Then 'Snapshot2' is taken after clicking 'Add items'. When comparing two snapshots using 'Comparison' option the 'Constructor' column shows 'Object' and 'Detached HTMLParagraphElement' on the top when sorted by 'Delta'. Each of the top two rows have atleast 10000 new items. This shows that clicking 'Add items' has allocated memory. On further inspection after switching to 'Summary' mode for 'Snapshot2' it is visible that 'Object' items are retained by 'arr' variable and 'Detached HTMLParagraphElement' items are retained by 'domNodes' variable.
Allocation Timelines
Allocation timelines are basically heap snapshots recorded over a period of time. To record allocation timelines open 'Chrome Menu'->'More Tools'->'Developer Tools'->'Memory'->'Allocation Instrumentation on Timelines'->'Start'.
<button id="add-items">Add items</button> <script> var arr = []; function grow() { //increase size of arr variable for (var i =0; i<10000; i++){ var obj = {i:i}; arr.push(obj); } } document.getElementById('add-items').addEventListener('click', grow); </script>
Allocation timelines are useful for studying particular actions over time that might be causing memory leak. After starting to record timeline the 'Add items' is clicked 5 times. The blue bars indicate the part of heap memory that has not been garbage collected(candidates for memory leak) while the gray part of the bars indicates that garbage collection has occurred. To investigate move the timing window to isolate any blue bar. In this case 10000 objects show in 'Constructor' column. Click any one of the objects this will show the retainers in the 'Retainers' column which shows that 'arr' variable has the reference to those objects.
Task Manager
To open 'Task Manager' open 'Chrome Menu'->'More Tools'->'Task Manager' in incognito mode. Make sure that only 'JavaScript memory' and 'Memory footprint' columns are visible.
<button id="add-items">Add items</button> <script> var arr = []; function grow() { setInterval(function(){ for (var i = 0; i < 100000; i++) { document.body.appendChild(document.createElement('p')); var obj = {i: i}; arr.push(obj); } }, 1500); } document.getElementById('add-items').addEventListener('click', grow); </script>
When 'Add items' button is clicked the 'Memory footprint' along with the 'JavaScript memory' column starts to show increasing numbers. The 'Memory footprint' is reflecting the native memory used to store DOM node elements which are increasing by 100000 every 1.5s. The 'JavaScript memory' reflects JS heap memory usage which are array and its items. For 'JavaScript memory' the value inside the brackets is live(used) memory and the value outside the bracket is reserved memory. If either 'JavaScript memory'(live) or 'Memory footprint' are increasing then there is memory leak. In case either 'JavaScript memory'(live) or 'Memory footprint' is frequently rising and falling then there is frequent garbage collection issue.