Fix memory problems

Learn how to use Chrome and DevTools to find memory issues that affect page performance, including memory leaks, memory bloat, and frequent garbage collections.

Summary

  • Find out how much memory your page is currently using with the Chrome Task Manager.
  • Visualize memory usage over time with Timeline recordings.
  • Identify detached DOM trees (a common cause of memory leaks) with Heap Snapshots.
  • Find out when new memory is being allocated in your JS heap with Allocation Timeline recordings.

Overview

In the spirit of the RAIL performance model, the focus of your performance efforts should be your users.

Memory issues are important because they are often perceivable by users. Users can perceive memory issues in the following ways:

  • A page's performance gets progressively worse over time. This is possibly a symptom of a memory leak. A memory leak is when a bug in the page causes the page to progressively use more and more memory over time.
  • A page's performance is consistently bad. This is possibly a symptom of memory bloat. Memory bloat is when a page uses more memory than is necessary for optimal page speed.
  • A page's performance is delayed or appears to pause frequently. This is possibly a symptom of frequent garbage collections. Garbage collection is when the browser reclaims memory. The browser decides when this happens. During collections, all script execution is paused. So if the browser is garbage collecting a lot, script execution is going to get paused a lot.

Memory bloat: how much is "too much"?

A memory leak is easy to define. If a site is progressively using more and more memory, then you've got a leak. But memory bloat is a bit harder to pin down. What qualifies as "using too much memory"?

There are no hard numbers here, because different devices and browsers have different capabilities. The same page that runs smoothly on a high-end smartphone might crash on a low-end smartphone.

The key here is to use the RAIL model and focus on your users. Find out what devices are popular with your users, and then test out your page on those devices. If the experience is consistently bad, the page may be exceeding the memory capabilities of those devices.

Monitor memory use in realtime with the Chrome Task Manager

Use the Chrome Task Manager as a starting point to your memory issue investigation. The Task Manager is a realtime monitor that tells you how much memory a page is currently using.

  1. Press Shift+Esc or go to the Chrome main menu and select More tools > Task manager to open the Task Manager.

    Opening the Task Manager

  2. Right-click on the table header of the Task Manager and enable JavaScript memory.

    Enabling JS memory

These two columns tell you different things about how your page is using memory:

  • The Memory column represents native memory. DOM nodes are stored in native memory. If this value is increasing, DOM nodes are getting created.
  • The JavaScript Memory column represents the JS heap. This column contains two values. The value you're interested in is the live number (the number in parentheses). The live number represents how much memory the reachable objects on your page are using. If this number is increasing, either new objects are being created, or the existing objects are growing.

Visualize memory leaks with Performance recordings

You can also use the Performance panel as another starting point in your investigation. The Performance panel helps you visualize a page's memory use over time.

  1. Open the Performance panel on DevTools.
  2. Enable the Memory checkbox.
  3. Make a recording.

To demonstrate Performance memory recordings, consider the code below:

var x = [];

function grow() {
  for (var i = 0; i < 10000; i++) {
    document.body.appendChild(document.createElement('div'));
  }
  x.push(new Array(1000000).join('x'));
}

document.getElementById('grow').addEventListener('click', grow);

Every time that the button referenced in the code is pressed, ten thousand div nodes are appended to the document body, and a string of one million x characters is pushed onto the x array. Running this code produces a Timeline recording like the following screenshot:

simple growth example

First, an explanation of the user interface. The HEAP graph in the Overview pane (below NET) represents the JS heap. Below the Overview pane is the Counter pane. Here you can see memory usage broken down by JS heap (same as HEAP graph in the Overview pane), documents, DOM nodes, listeners, and GPU memory. Disabling a checkbox hides it from the graph.

Now, an analysis of the code compared with the screenshot. If you look at the node counter (the green graph) you can see that it matches up cleanly with the code. The node count increases in discrete steps. You can presume that each increase in the node count is a call to grow(). The JS heap graph (the blue graph) is not as straightforward. In keeping with best practices, the first dip is actually a forced garbage collection (achieved by pressing the collect garbage button). As the recording progresses you can see that the JS heap size spikes. This is natural and expected: the JavaScript code is creating the DOM nodes on every button click and doing a lot of work when it creates the string of one million characters. The key thing here is the fact that the JS heap ends higher than it began (the "beginning" here being the point after the forced garbage collection). In the real world, if you saw this pattern of increasing JS heap size or node size, it would potentially mean a memory leak.

Discover detached DOM tree memory leaks with Heap Snapshots

A DOM node can only be garbage collected when there are no references to it from either the page's DOM tree or JavaScript code. A node is said to be "detached" when it's removed from the DOM tree but some JavaScript still references it. Detached DOM nodes are a common cause of memory leaks. This section teaches you how to use DevTools' heap profilers to identify detached nodes.

Here's a simple example of detached DOM nodes.

var detachedTree;

function create() {
  var ul = document.createElement('ul');
  for (var i = 0; i < 10; i++) {
    var li = document.createElement('li');
    ul.appendChild(li);
  }
  detachedTree = ul;
}

document.getElementById('create').addEventListener('click', create);

Clicking the button referenced in the code creates a ul node with ten li children. These nodes are referenced by the code but do not exist in the DOM tree, so they're detached.

Heap snapshots are one way to identify detached nodes. As the name implies, heap snapshots show you how memory is distributed among your page's JS objects and DOM nodes at the point of time of the snapshot.

To create a snapshot, open DevTools and go to the Memory panel, select the Heap Snapshot radio button, and then press the Take Snapshot button.

take heap snapshot

The snapshot may take some time to process and load. Once it's finished, select it from the lefthand panel (named HEAP SNAPSHOTS).

Type Detached in the Class filter textbox to search for detached DOM trees.

filtering for detached nodes

Expand the carats to investigate a detached tree.

investigating detached tree

Nodes highlighted yellow have direct references to them from the JavaScript code. Nodes highlighted red do not have direct references. They are only alive because they are part of the yellow node's tree. In general, you want to focus on the yellow nodes. Fix your code so that the yellow node isn't alive for longer than it needs to be, and you also get rid of the red nodes that are part of the yellow node's tree.

Click on a yellow node to investigate it further. In the Objects pane you can see more information about the code that's referencing it. For example, in the screenshot below you can see that the detachedTree variable is referencing the node. To fix this particular memory leak, you would study the code that uses detachedTree and ensure that it removes its reference to the node when it's no longer needed.

investigating a yellow node

Identify JS heap memory leaks with Allocation Timelines

The Allocation Timeline is another tool that can help you track down memory leaks in your JS heap.

To demonstrate the Allocation Timeline consider the following code:

var x = [];

function grow() {
  x.push(new Array(1000000).join('x'));
}

document.getElementById('grow').addEventListener('click', grow);

Every time that the button referenced in the code is pushed, a string of one million characters is added to the x array.

To record an Allocation Timeline, open DevTools, go to the Profiles panel, select the Record Allocation Timeline radio button, press the Start button, perform the action that you suspect is causing the memory leak, and then press the stop recording button (stop recording button) when you're done.

As you're recording, notice if any blue bars show up on the Allocation Timeline, like in the screenshot below.

new allocations

Those blue bars represent new memory allocations. Those new memory allocations are your candidates for memory leaks. You can zoom on a bar to filter the Constructor pane to only show objects that were allocated during the specified timeframe.

zoomed allocation timeline

Expand the object and click on its value to view more details about it in the Object pane. For example, in the screenshot below, by viewing the details of the object that was newly allocated, you'd be able to see that it was allocated to the x variable in the Window scope.

object details

Investigate memory allocation by function

Use the Allocation Sampling type in the Memory panel to view memory allocation by JavaScript function.

Record Allocation Profiler

  1. Select the Allocation Sampling radio button. If there is a worker on the page, you can select that as the profiling target using the dropdown menu next to the Start button.
  2. Press the Start button.
  3. Perform the actions on the page which you want to investigate.
  4. Press the Stop button when you have finished all of your actions.

DevTools shows you a breakdown of memory allocation by function. The default view is Heavy (Bottom Up), which displays the functions that allocated the most memory at the top.

Allocation profile

Spot frequent garbage collections

If your page appears to pause frequently, then you may have garbage collection issues.

You can use either the Chrome Task Manager or Timeline memory recordings to spot frequent garbage collections. In the Task Manager, frequently rising and falling Memory or JavaScript Memory values represent frequent garbage collections. In Timeline recordings, frequently rising and falling JS heap or node count graphs indicate frequent garbage collections.

Once you've identified the problem, you can use an Allocation Timeline recording to find out where memory is being allocated and which functions are causing the allocations.