This content originally appeared on web.dev and was authored by Ulan Degenbaev
Browsers manage the memory of web pages automatically. Whenever a web page creates an object, the browser allocates a chunk of memory "under the hood" to store the object. Since memory is a finite resource, the browser performs garbage collection to detect when an object is no longer needed and to free the underlying memory chunk. The detection is not perfect though, and it was proven that perfect detection is an impossible task. Therefore browsers approximate the notion of "an object is needed" with the notion of "an object is reachable". If the web page cannot reach an object via its variables and the fields of other reachable objects, then the browser can safely reclaim the object. The difference between these two notions leads to memory leaks as illustrated by the following example.
const object = { a: new Array(1000), b: new Array(2000) };
setInterval(() => console.log(object.a), 1000);
Here the larger array b
is no longer needed, but the browser does not
reclaim it because it is still reachable via object.b
in the callback. Thus
the memory of the larger array is leaked.
Memory leaks are prevalent on the Web. It is easy to introduce one by forgetting to unregister an event listener, by accidentally capturing objects from an iframe, by not closing a worker, by accumulating objects in arrays, and so on. If a web page has memory leaks, then its memory usage grows over time and the web page appears slow and bloated to the users.
The first step in solving this problem is measuring it. The new
performance.measureUserAgentSpecificMemory()
API allows developers to
measure memory usage of their web pages in production and thus detect memory
leaks that slip through local testing.
How is performance.measureUserAgentSpecificMemory()
different from the legacy performance.memory
API?
If you are familiar with the existing non-standard performance.memory
API,
you might be wondering how the new API differs from it. The main difference is
that the old API returns the size of the JavaScript heap whereas the new API
estimates the memory usage of the entire web page. This difference becomes
important when Chrome shares the same heap with multiple web pages (or
multiple instances of the same web page). In such cases, the result of the old
API may be arbitrarily off. Since the old API is defined in
implementation-specific terms such as "heap", standardizing it is hopeless.
Another difference is that the new API performs memory measurement during garbage collection. This reduces the noise in the results, but it may take a while until the results are produced. Note that other browsers may decide to implement the new API without relying on garbage collection.
Suggested use cases
Memory usage of a web page depends on the timing of events, user actions, and garbage collections. That is why the memory measurement API is intended for aggregating memory usage data from production. The results of individual calls are less useful. Example use cases:
- Regression detection during rollout of a new version of the web page to catch new memory leaks.
- A/B testing a new feature to evaluate its memory impact and detect memory leaks.
- Correlating memory usage with session duration to verify presence or absence of memory leaks.
- Correlating memory usage with user metrics to understand the overall impact of memory usage.
Browser compatibility
Currently the API is supported only in Chrome 83 as an origin trial. The result of the API is highly implementation-dependent because browsers have different ways of representing objects in memory and different ways of estimating the memory usage. Browsers may exclude some memory regions from accounting if proper accounting is too expensive or infeasible. Thus, results cannot be compared across browsers. It is only meaningful to compare the results for the same browser.
Current status
Step | Status |
---|---|
1. Create explainer | Complete |
2. Create initial draft of specification | In Progress |
3. Gather feedback and iterate design | In Progress |
4. Origin trial | In Progress |
5. Launch | Not Started |
Using performance.measureUserAgentSpecificMemory()
Enabling via chrome://flags
To experiment with performance.measureUserAgentSpecificMemory()
without an origin trial
token, enable the #experimental-web-platform-features
flag in chrome://flags
.
Enabling support during the origin trial phase
The performance.measureUserAgentSpecificMemory()
API is available as an origin trial starting in
Chrome 83. The origin trial is expected to end in Chrome 86, in early November 2020.
Origin trials allow you to try new features and give feedback on their usability, practicality, and effectiveness to the web standards community. For more information, see the Origin Trials Guide for Web Developers. To sign up for this or another origin trial, visit the registration page.
Register for the origin trial
- Request a token for your origin.
- Add the token to your pages. There are two ways to do that:
- Add an
origin-trial
<meta>
tag to the head of each page. For example, this may look something like:
<meta http-equiv="origin-trial" content="TOKEN_GOES_HERE">
- If you can configure your server, you can also add the token
using an
Origin-Trial
HTTP header. The resulting response header should look something like:
Origin-Trial: TOKEN_GOES_HERE
- Add an
Feature detection
The performance.measureUserAgentSpecificMemory()
function may fail with a
SecurityError if the execution environment does not fulfil
the security requirements for preventing cross-origin information leaks.
During the origin trial in Chrome, the API requires that Site
Isolation is enabled. When the API ships, it will rely on
cross-origin isolation. A web page can opt-in to
cross-origin isolation by setting COOP+COEP headers.
if (performance.measureUserAgentSpecificMemory) {
let result;
try {
result = await performance.measureUserAgentSpecificMemory();
} catch (error) {
if (error instanceof DOMException &&
error.name === "SecurityError") {
console.log("The context is not secure.");
} else {
throw error;
}
}
console.log(result);
}
Local testing
Chrome performs the memory measurement during garbage collection. This means
that the API does not resolve the result promise immediately and instead waits
for the next garbage collection. The API forces a garbage collection after
some timeout, which is currently set to 20 seconds. Starting Chrome with the
--enable-blink-features='ForceEagerMeasureMemory'
command-line flag reduces
the timeout to zero and is useful for local debugging and testing.
Example
The recommended usage of the API is to define a global memory monitor that
samples memory usage of the whole web page and sends the results to a server
for aggregation and analysis. The simplest way is to sample periodically, for
example every M
minutes. That however introduces bias to data because the
memory peaks may occur between the samples. The following example shows how to
do unbiased memory measurements using a Poisson process, which
guarantees that samples are equally likely to occur at any point in time
(demo, source).
First, define a function that schedules the next memory measurement using
setTimeout()
with a randomized interval. The function should be called after
page load on the main window.
function scheduleMeasurement() {
if (!performance.measureUserAgentSpecificMemory) {
console.log("performance.measureUserAgentSpecificMemory() is not available.");
return;
}
const interval = measurementInterval();
console.log("Scheduling memory measurement in " +
Math.round(interval / 1000) + " seconds.");
setTimeout(performMeasurement, interval);
}
// Start measurements after page load on the main window.
window.onload = function () {
scheduleMeasurement();
}
The measurementInterval()
function computes a random interval in milliseconds
such that on average there is one measurement every five minutes. See Exponential
distribution if you are interested in the math behind the function.
function measurementInterval() {
const MEAN_INTERVAL_IN_MS = 5 * 60 * 1000;
return -Math.log(Math.random()) * MEAN_INTERVAL_IN_MS;
}
Finally, the async performMeasurement()
function invokes the API, records
the result, and schedules the next measurement.
async function performMeasurement() {
// 1. Invoke performance.measureUserAgentSpecificMemory().
let result;
try {
result = await performance.measureUserAgentSpecificMemory();
} catch (error) {
if (error instanceof DOMException &&
error.name === "SecurityError") {
console.log("The context is not secure.");
return;
}
// Rethrow other errors.
throw error;
}
// 2. Record the result.
console.log("Memory usage:", result);
// 3. Schedule the next measurement.
scheduleMeasurement();
}
The result may look as follows:
// Console output:
{
bytes: 60_000_000,
breakdown: [
{
bytes: 40_000_000,
attribution: [
{
url: "https://foo.com",
scope: "Window",
},
]
types: ["JS"]
},
{
bytes: 0,
attribution: [],
types: []
},
{
bytes: 20_000_000,
attribution: [
{
url: "https://foo.com/iframe",
container: {
id: "iframe-id-attribute",
src: "redirect.html?target=iframe.html",
},
},
],
types: ["JS"]
},
]
}
The total memory usage estimate is returned in the bytes
field. The value of
bytes is using numeric separator syntax. This value is
highly implementation-dependent and cannot be compared across browsers. It may
even change between different versions of the same browser. During the origin
trial the value includes JavaScript memory usage of the main window and all
same-site iframes and related windows. When the API ships, the value will
account for JavaScript and DOM memory of all iframes, related windows, and web
workers in the current process.
The breakdown
list provides further information about the used memory. Each
entry describes some portion of the memory and attributes it to a set of
windows, iframes, and workers identified by URLs. The types
field lists
the implementation-specific memory types associated with the memory.
It is important to treat all lists in a generic way and to not hardcode
assumptions based on a particular browser. For example, some browsers may
return an empty breakdown
or an empty attribution
. Other browsers may
return multiple entries in attribution
indicating they could not distinguish
which of these entries owns the memory.
Feedback
The Web Performance Community Group and the Chrome team would love
to hear about your thoughts and experiences with
performance.measureUserAgentSpecificMemory()
.
Tell us about the API design
Is there something about the API that doesn't work as expected? Or are there missing properties that you need to implement your idea? File a spec issue on the performance.measureUserAgentSpecificMemory() GitHub repo or add your thoughts to an existing issue.
Report a problem with the implementation
Did you find a bug with Chrome's implementation? Or is the implementation
different from the spec? File a bug at new.crbug.com. Be sure to
include as much detail as you can, provide simple instructions for reproducing
the bug, and have Components set to Blink>PerformanceAPIs
.
Glitch works great for sharing quick and easy repros.
Show support
Are you planning to use performance.measureUserAgentSpecificMemory()
? Your public support
helps the Chrome team prioritize features and shows other browser vendors how
critical it is to support them. Send a tweet to @ChromiumDev and let us know
where and how you're using it.
Helpful links
Acknowledgements
Big thanks to Domenic Denicola, Yoav Weiss, Mathias Bynens for API design reviews, and Dominik Inführ, Hannes Payer, Kentaro Hara, Michael Lippautz for code reviews in Chrome. I also thank Per Parker, Philipp Weis, Olga Belomestnykh, Matthew Bolohan, and Neil Mckay for providing valuable user feedback that greatly improved the API.
Hero image by Harrison Broadbent on Unsplash
This content originally appeared on web.dev and was authored by Ulan Degenbaev

Ulan Degenbaev | Sciencx (2020-04-13T00:00:00+00:00) Monitor your web page’s total memory usage with `measureMemory()`. Retrieved from https://www.scien.cx/2020/04/13/monitor-your-web-pages-total-memory-usage-with-measurememory/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.