- We’ve open-sourced MemLab, a JavaScript reminiscence testing framework that automates reminiscence leak detection.
- Finding and addressing the basis reason behind reminiscence leaks is vital for delivering a top quality person expertise on net functions.
- MemLab has helped engineers and builders at Meta enhance person expertise and make important enhancements in reminiscence optimization. We hope it’ll do the identical for the bigger JavaScript neighborhood as properly.
In 2020, we redesigned Facebook.com as a single-page software (SPA) that does most of its rendering and navigation utilizing client-side JavaScript. We used an identical structure to construct most of Meta’s different in style net apps, together with Instagram and Office. And whereas this structure permits us to offer quicker person interactions, a greater developer expertise, and a extra app-like really feel, sustaining the net app state on the shopper makes successfully managing client-side reminiscence extra complicated.
Folks utilizing our net apps will usually discover efficiency and practical correctness points instantly. A reminiscence leak, nonetheless, is a unique story. It isn’t instantly perceivable, as a result of it eats up a bit of reminiscence at a time — affecting the whole net session and making subsequent interactions slower and fewer responsive.
To assist our builders deal with this, we constructed MemLab, a JavaScript reminiscence testing framework that automates leak detection and makes it simpler to root-cause reminiscence leaks. We’ve used MemLab at Meta to efficiently include unsustainable reminiscence will increase and determine reminiscence leaks and reminiscence optimization alternatives throughout our merchandise and infra.
We’ve already open-sourced MemLab on GitHub, and we’re excited to work with the JavaScript neighborhood and have builders begin utilizing MemLab in the present day.
Why we developed MemLab
Traditionally, we spent a number of time measuring, optimizing, and controlling page load and interaction time, in addition to JavaScript code measurement. We constructed automated techniques that alerted us when there have been regressions in these metrics — each earlier than and after code landed in manufacturing — in order that we may act rapidly to repair these points and forestall the modifications from ever touchdown in manufacturing.
Comparatively, we hadn’t executed a lot work for managing net in-browser reminiscence. And once we analyzed the brand new Fb.com’s reminiscence utilization, we discovered that each reminiscence utilization and the variety of out-of-memory (OOM) crashes on the shopper aspect had been climbing.
Excessive reminiscence utilization has a statistically important and adverse influence on:
- Web page load and interplay efficiency (how a lot time it takes to load a web page or carry out an interplay)
- Consumer engagement metrics (energetic customers, time spent on web site, variety of actions carried out)
What causes excessive reminiscence utilization in net functions?
As a result of reminiscence leaks aren’t normally apparent, they seldom get caught in code evaluate, are arduous to identify throughout growth, and are sometimes tough to root-cause in manufacturing. However mainstream JavaScript runtimes all have rubbish collectors, so how is reminiscence leaking within the first place?
JavaScript code can expertise reminiscence leaks by retaining hidden references to things. Hidden references may cause reminiscence leaks in lots of sudden methods.
For instance:
var obj = ;
console.log(obj);
obj = null;
In Chrome, this code leaks obj regardless that we set the reference to null. This occurs as a result of Chrome must hold an inner reference to the printed object in order that it may be inspected within the net console later (even when the net console shouldn’t be opened).
There will also be circumstances the place reminiscence shouldn’t be technically leaked however grows linearly and unbounded throughout a person session. The most typical causes of this are client-side caches that don’t have any eviction logic inbuilt and infinite scroll lists that don’t have any virtualization to take away earlier objects from the record as new content material is added.
We additionally didn’t have automated techniques and processes in place to manage reminiscence, so the one protection in opposition to regressions was consultants periodically digging into reminiscence leaks by way of Chrome DevTools, which was not scalable contemplating we’re transport a nontrivial variety of modifications day-after-day.
How MemLab works
In a nutshell, MemLab finds reminiscence leaks by working a headless browser by way of predefined take a look at eventualities and diffing and analyzing the JavaScript heap snapshots.
This course of occurs in six steps:
1. Browser interplay
To seek out leaked objects on a goal web page (B). MemLab automates a browser utilizing Puppeteer and visits the take a look at pages within the following order:
- Navigate to a unique tab (A) and get heap SA.
- Navigate to the goal web page (B) and get heap SB.
- Come again to the earlier web page (A’) and get heap SA’.
2. Diffing the heap
Once we navigate to a web page after which navigate away from it, we’d anticipate a lot of the reminiscence allotted by that web page to even be freed — if not, it’s extremely suggestive of a reminiscence leak. MemLab finds potential reminiscence leaks by diffing the JavaScript heap and recording the set of objects allotted on web page B that weren’t allotted on Web page A however are nonetheless current when Web page A is reloaded. Or, extra formally, the superset of objects leaked from the goal web page will be derived as (SB SA) ∩ SA’ ).
3. Refining the record of reminiscence leaks
The leak detector additional incorporates framework-specific data to refine the record of leaked objects. For instance, Fiber nodes allotted by React (an interior knowledge construction React makes use of for rendering the digital DOM) must be launched once we clear up after visiting a number of tabs.
4. Producing retainer traces
MemLab traverses the heap and generates retainer traces for every leaked object. A retainer hint is an object reference chain from the GC roots (the entry objects in a heap graph from which rubbish collectors traverse the heap) to a leaked object. The hint exhibits why and the way a leaked object is stored alive in reminiscence. Breaking the reference chain means the leaked object will now not be reachable from the GC root and subsequently will be rubbish collected. By following the retainer hint one step at a time, yow will discover the reference that must be set to null (however wasn’t, resulting from a bug).
5. Clustering retainer traces
Typically sure interactions can set off 1000’s of leaked objects. It will be overwhelming to indicate all of the retainer traces on this case. MemLab clusters all retainer traces and exhibits one hint for every cluster of leaked objects that share related retainer traces. The hint additionally consists of debug info, equivalent to dominator nodes and retained sizes.
6. Reporting the leaks
We run MemLab at common intervals all through the day to get a steady sign on reminiscence regressions. Any new regressions are added to an inner dashboard, the place clustered retainer traces of all reminiscence leaks detected are gathered and categorized. Builders can then click on and think about the properties of objects on the retainer hint of every reminiscence leak.
(Word: This dashboard shouldn’t be a part of the open supply launch of MemLab, however one thing related might be added to any CI/CD pipeline.)
MemLab’s options
Reminiscence leak detection
For in-browser reminiscence leak detection, the one enter MemLab requires from builders is a test scenario file that defines how to interact with the webpage by overriding three callbacks with the Puppeteer API and CSS selectors. MemLab robotically diffs the JavaScript heap, refines reminiscence leaks, and aggregates outcomes.
Graph-view API of JavaScript heap
MemLab helps a self-defined leak detector as a filter callback that’s utilized to every leak candidate object allotted by the goal interplay however by no means launched afterwards. The leak filter callback can traverse the heap and determine which objects are reminiscence leaks. For instance, our built-in leak detector follows the return chain of a React Fiber node and checks if the Fiber node is indifferent from the React Fiber tree.
To permit the context of every candidate leak to be analyzed, MemLab offers a memory-efficient graph view of the JavaScript heap. This permits querying and traversing the JavaScript heap with out having any area data about V8’s heap snapshot file construction.
Within the graph view, every JavaScript object or native object within the heap is a graph node, and every JavaScript reference within the heap is a graph edge. The heap measurement of an actual software is commonly giant, so the graph view must be memory-efficient whereas offering an intuitive object-oriented heap-traversal API. Due to this fact, the graph nodes are designed to be digital and never interconnected by way of JavaScript references. When the evaluation code traverses the heap, the digital graph view partially constructs the touched part of the graph just-in-time. Any a part of the graph will be simply deallocated since these digital nodes don’t have JavaScript references to one another.
The heap graph view will be loaded from JavaScript heap snapshots taken from Chromium-based browsers, Node.js, Electron, and Hermes. This permits for complicated patterns to be analyzed and solutions questions equivalent to, “What number of React Fiber nodes are alternate Fiber nodes, that are utilized in incomplete concurrent renders?” or “What’s the complete retained measurement of unmounted react elements?”.
import getHeapFromFile from '@memlab/heap-analysis';
const heapGraph = await getHeapFromFile(heapFile);
heapGraph.nodes.forEach(node => {
// heap node traversal
node.sort
node.references
);
Reminiscence assertions
A Node.js program or Jest take a look at may use the graph-view API to get a heap graph view of its personal state, to do self-memory checking, and write all kinds of memory assertions.
import sort IHeapSnapshot from '@memlab/core';
import config, takeNodeMinimalHeap, tagObject from '@memlab/core';
take a look at('reminiscence take a look at', async () =>
config.muteConsole = true;
const o1 = ;
let o2 = ;
// tag o1 with marker: "memlab-mark-1", doesn't modify o1 in any manner
tagObject(o1, 'memlab-mark-1');
// tag o2 with marker: "memlab-mark-2", doesn't modify o2 in any manner
tagObject(o2, 'memlab-mark-2');
o2 = null;
const heap: IHeapSnapshot = await takeNodeMinimalHeap();
// anticipate object with marker "memlab-mark-1" exists
anticipate(heap.hasObjectWithTag('memlab-mark-1')).toBe(true);
// anticipate object with marker "memlab-mark-2" will be GCed
anticipate(heap.hasObjectWithTag('memlab-mark-2')).toBe(false);
, 30000);
Reminiscence toolbox
Along with reminiscence leak detection, MemLab features a set of built-in CLI commands and APIs for locating reminiscence optimization alternatives:
- Break down heap by object shapes (e.g., arguments, postRun, preRun, fairly, thisProgram, …) as an alternative of classifying objects based mostly on constructor names (e.g., Object). That is helpful to rapidly detect and root-cause important reminiscence utilized by object literals.
- Detect continuous individual object growth or object shape growth. MemLab can take a collection of heap snapshots as enter and discover which object or class of objects retains rising in measurement over time.
- Find duplicate string instances. V8 doesn’t at all times do string interning optimization, which implies two JavaScript string primitives with the identical worth might be represented by two completely different native objects in V8’s heap.


Utilizing MemLab at Meta: Case research
Over the previous few years, we’ve used MemLab to detect and diagnose reminiscence leaks and have gathered insights which have helped us optimize reminiscence, considerably enhance reminiscence and reliability (lowering OOM crashes), and enhance person expertise.

React Fiber node cleanup
For rendered elements, React builds a Fiber tree — an interior knowledge construction React makes use of for rendering the digital DOM. Though the Fiber tree appears like a tree, it’s a bidirectional graph that strongly connects all Fiber nodes, React part situations, and the related HTML DOM parts. Ideally, React maintains references to the basis of the part’s Fiber tree and retains the Fiber tree from being rubbish collected. When a part is unmounted, React breaks the connection between the host root of the part and the remainder of the Fiber tree, which may then be rubbish collected.
The draw back of getting a strongly related graph is that if there may be any exterior reference pointing to any a part of the graph, the entire graph can’t be rubbish collected. For instance, the next export assertion caches React elements on the module scope degree, so the related Fiber tree and indifferent DOM parts are by no means launched.
export const Element = ((
<Listing> ... </Listing>
): React.Component<typeof Listing>);
It’s additionally not simply the React knowledge buildings that may be stored alive. Hooks and their closures may hold alive every kind of different objects. Because of this a single React part leak may trigger the leak of a major a part of a web page’s objects, main to large reminiscence leaks.
To forestall the cascading impact of reminiscence leaks within the Fiber tree, we added a full traversal of a tree that does aggressive cleanup when a part is unmounted in React 18 (because of Benoit Girard and the React group). This permits the rubbish collector to do a greater job at cleansing up an unmounted tree. Any unintended reminiscence leak is bounded to a smaller leak measurement. This repair decreased common reminiscence utilization on Fb by virtually 25 p.c, and we noticed giant enhancements throughout different websites utilizing React once they upgraded. We had been anxious that this aggressive cleanup may decelerate unmounting in React, however surprisingly, we noticed a major efficiency win due to reminiscence reductions.
Relay string interning
By leveraging the heap evaluation APIs in MemLab, we discovered that strings occupied 70 p.c of the heap, and half of these strings had at the very least one duplicated occasion. (V8 doesn’t at all times do string interning, which is an optimization that deduplicates string situations with the identical worth.)
Once we additional queried duplicated string patterns and clustering retainer traces we discovered that a good portion of string reminiscence is consumed by cached key strings in Relay. Relay generates cached keys for fragments by doing duplication, serialization, and concatenation. The string keys are additional copied and concatenated for cached keys of different sources (e.g., fragments) in Relay. By collaborating with the Relay and React Apps groups, we optimized Relay cache key strings by interning and shortening overlong string keys on the shopper aspect.
This optimization enabled Relay to cache extra knowledge, permitting the location to indicate extra content material to customers, particularly when there may be restricted RAM on the shopper aspect. We noticed a 20 p.c discount in reminiscence p99 in addition to OOM crashes, quicker web page rendering, improved person expertise, and income wins.
Attempt MemLab in the present day
We’re excited to open-source MemLab and have builders begin utilizing it. We’re particularly taken with seeing which use circumstances the neighborhood finds helpful.
You’ll be able to set up MemLab by way of npm or construct it from the GitHub repo:
npm i -g memlab
We even have a quick start guide to assist builders get began.
In the event you’ve tried MemLab out by yourself venture, please reach out and tell us the way it labored for you!
Acknowledgements
We’d prefer to thank Tulga Narmandakh and the remainder of the Core Well being Expertise group for his or her contributions towards serving to open-source MemLab.