Memory and speed
How garbage collection pauses, code that makes too many objects, low-memory phones and tab discards slow pages down or wipe them, measured in Chrome.
- 01Find clean-up pauses in your framesRead Minor GC entries in a trace, and tell why a short pause still costs a frame when the frame is already full.
- 02Make less garbage in busy codeReuse arrays, make labels once, and keep code that runs every frame free of new objects.
- 03Keep the visitor's work when a tab is thrown awaySave on visibilitychange, read it back when the page starts, and count discards with document.wasDiscarded.
Why did Ben's shelf stutter, and where did his cart go?
Ben shops at the Leaf and Kettle on a cheap phone. He opens All teas. The page shows a shelf of tea cards, three at a time. He taps › to slide the shelf on to the next three teas. The cards should glide across in 0.6 seconds. This lesson calls each slide a glide.
On his phone, the shelf doesn't glide smoothly. It jerks a little as it moves. Then Ben adds three teas to his cart. He switches to WhatsApp to ask Meera which tea she wants. When he comes back, the page loads again from the top. His cart is empty.
So Ben has two questions. Why does the shelf jerk? And where did his cart go? Both answers are about memory. Memory is the space where a page keeps its data while it runs: every list, every object, every bit of text.
First, how a slide works. The screen shows a quick run of still pictures, called frames. On a 60 Hz screen, a smooth slide needs a new frame every 16.7 ms, as lesson 6.2 showed. A millisecond (ms) is a thousandth of a second. When a frame isn't ready in time, the browser skips it, and the slide jumps.
Ravi built two versions of the shelf. They look the same and show the same cards. The first makes new objects for every tea, in every frame. The second makes its objects once and reuses them. The course recorded Ben's taps on each. Here is what Ben saw on his second tap:
Each tick is one new picture on Ben's screen. On the top row, the gaps are a little wider, and 3 frames were dropped: 35 pictures instead of 38. Each gap is a small jump in the slide. Here is what all five taps cost:
// Ben's five taps on the tea shelf, recorded in Chrome with a 4x slower CPUconst makesNew = { pictures: 178, dropped: 11, cleanups: 79 };const reuses = { pictures: 189, dropped: 0, cleanups: 0 }; for (const [name, shelf] of Object.entries({ makesNew, reuses })) { console.log(`${name}: ${shelf.pictures} pictures, ${shelf.dropped} dropped, ${shelf.cleanups} cleanups`);}- Line 1 is a comment. It says where the numbers come from.
- Lines 2 and 3 hold three numbers for each shelf.
picturescounts the new pictures Chrome put on the screen while the shelf moved.droppedcounts the frames it had to skip.cleanupscounts how often the browser cleaned up memory. The next section explains what that means. - Lines 5 to 7 print one line per shelf:
makesNew: 178 pictures, 11 dropped, 79 cleanupsandreuses: 189 pictures, 0 dropped, 0 cleanups.
The shelf that makes new objects skipped 11 frames. It also cleaned up 79 times. The shelf that reuses its objects skipped none, and never cleaned up while it moved. Why it matters: a few skipped frames in each slide are enough to make a shop feel cheap.
A small café has one barista. She makes every drink, and she washes every cup. Washing one cup is quick. But while she washes, no drink gets made. If the café hands out a fresh cup for every sip, she washes all day, and the drinks come late.
- In real life: The one barista
- In JavaScript: The page's main thread
- In real life: Making each drink
- In JavaScript: Running the page's code and drawing each frame
- In real life: Washing the cups
- In JavaScript: Garbage collection: cleaning up memory
- In real life: A fresh cup for every sip
- In JavaScript: Code that makes new objects in every frame
Every object a page makes takes memory. Later, the browser cleans it up on the same main thread that draws the page. And a phone short of memory can throw away a tab you aren't looking at. So make fewer new objects in code that runs often, and save what the visitor would miss.
How the course recorded it
- Two shelves: the 3,000 teas from lesson 28.2, three cards in view. Ben taps › five times, once a second.
- Ben's phone: this computer in Chrome 157, with its processor, the CPU, slowed 4 times. V8, Chrome's JavaScript engine, got the smaller space for new objects that it uses on Android phones. The section on phones explains it.
- The same other work: each frame of both shelves also runs
work(), the course's stand-in loop, for the rest of the frame's drawing. It took about 11.9 ms a frame. - Real taps: mouse presses sent through Chrome's DevTools protocol, which Chrome passes straight to the page.
- Three visits each, taking turns. The lesson quotes the middle visit, by frames dropped.
- The cart: Chrome's own test page, chrome://discards, threw Ben's tab away for real.
Cleaning up memory pauses the page
JavaScript gives memory back by itself. The part of the engine that does it is the garbage collector, or GC. It finds objects that no code can reach any more, and frees their space. Each run of it is a garbage collection. This lesson calls it a clean-up.
How the GC decides what to keep is taught in the JavaScript course: Memory management & garbage collection and Why garbage collection?. Here, we look at what Ben feels.
V8 has two kinds of clean-up. DevTools shows them by name:
- Minor GC cleans only the young generation: the small space where new objects start. Most objects die young, so this is quick. Generational GC explains why.
- Major GC cleans the whole heap, all the memory that holds the page's objects. Helper threads do much of its work while the page runs. But it still has to pause the page at the end.
Both stop the main thread for a while. That's the one thread that runs your code and draws the page. V8 has helper threads that share some of the work. It also tries to clean up in quiet moments between frames. You can read how in Incremental and concurrent GC. But some of the work always happens on the main thread, while the page waits.
At a sushi bar, the belt never waits. An empty slot passes the chef every few seconds. If a plate is ready, it goes on. If not, the slot goes by empty, and the plate waits for the next one. A chef who stops to wipe the counter only loses a moment. But if he was already late, that moment costs a slot.
- In real life: A slot on the belt, every few seconds
- In JavaScript: A frame, every 16.7 ms
- In real life: The chef
- In JavaScript: The main thread
- In real life: A plate ready in time
- In JavaScript: A frame drawn on time
- In real life: An empty slot going past
- In JavaScript: A dropped frame
- In real life: Wiping the counter between plates
- In JavaScript: A clean-up in the middle of a frame
Here is one frame of Ben's second glide on the shelf that makes new objects:
// One frame of Ben's second glide on the makes-new shelf, in msconst frame = { making: 3.5, cleaning: 1.1, rest: 11.7 };const script = frame.making + frame.cleaning + frame.rest;console.log(`Script: ${script.toFixed(1)} ms. Left to draw the frame: ${(16.7 - script).toFixed(1)} ms.`);- Line 2 holds three parts of the frame's script, from the recording.
makingis the time spent making the card objects.cleaningis a Minor GC that ran in the middle of it.restis everything else the frame's code did. - Line 3 adds them up.
- Line 4 prints
Script: 16.3 ms. Left to draw the frame: 0.4 ms.
The clean-up itself was short. But the frame was nearly full before it started. After the script, the browser still has to work out styles, lay out the cards and paint them, as lesson 6.3 showed. There was almost no time left for that.
That frame was not rare. In the five glides, the shelf cleaned up 79 times. Every one was a Minor GC. The middle one took 0.8 ms, and the longest 1.7 ms. 74 of them ran inside a frame, in the middle of the shelf's drawing code. The helper threads did 0.6 ms of the work in all. The main thread did the rest.
Frames with a clean-up usually had about 16.3 ms of script. Frames without one usually had about 15.5 ms. Here are the first frames of the glide, for both shelves:
On the left, every bar ends close to the line. On the right, every bar ends well before it. Why it matters: one clean-up is rarely long. It hurts when clean-ups come often, inside frames that are already full. On a cheap phone, frames often are.
Pick a glide to compare the two shelves, glide by glide:
- New pictures on screen
3538- Frames dropped
30- Clean-ups
180- Time cleaning up
10.6 ms0.0 ms- Making the shelf, per frame
3.7 ms0.1 ms- Script, middle frame
15.9 ms12.0 ms
Clean-ups counts the minor collections that started during the glide. Script is the middle frame's script, before the browser draws the frame.
Code that makes new things every frame
Making space for a new object is called allocation. Here is the drawing code of the first shelf. It runs once per frame, so about 60 times a second while the shelf moves:
function drawShelf(offset) { const cards = teas.map((tea, i) => ({ name: tea.name, x: i * CARD - offset, sizes: SIZES.map((grams) => `${grams} g £${(tea.price * grams / 100).toFixed(2)}`), })); let slot = 0; for (const card of cards.filter((card) => card.x > -CARD && card.x < VIEW)) show(slot++, card.name, card.x, card.sizes); hideFrom(slot);}- Line 1 starts
drawShelf.offsetis how far the shelf has slid, in pixels. - Lines 2 to 6 make a new card object for each of the 3,000 teas. Each card gets a name and a place.
- Line 5 also makes a new array of three size labels for each tea, like
100 g £4.50. That's three new strings per tea, per frame. - Line 8 keeps only the cards that are on the shelf, in a new array, and shows each one. Line 9 hides the card slots it didn't use.
Count the new things in one frame. There are 3,000 objects, 3,000 arrays, at least 9,000 strings and one more array. The shelf shows five cards at most. Everything else is garbage before the frame is even drawn.
A customer asks the price of mint tea. The waiter prints the whole 3,000-line menu, reads one line, and throws the menu in the bin. Then the next customer asks. Printing takes time, and the bin fills fast. Someone has to stop and empty it.
- In real life: Printing the whole menu
- In JavaScript: Making a card object for all 3,000 teas
- In real life: Reading one line
- In JavaScript: Showing the five cards on the shelf
- In real life: The bin filling up
- In JavaScript: The young generation filling up
- In real life: Emptying the bin
- In JavaScript: A Minor GC
- In real life: One menu, pointing at a line
- In JavaScript: One array, made once and reused
The second shelf does the same job without making new objects for the teas in each frame:
const places = new Float64Array(teas.length);const sizes = teas.map((tea) => SIZES.map((grams) => `${grams} g £${(tea.price * grams / 100).toFixed(2)}`));function drawShelf(offset) { let slot = 0; for (let i = 0; i < teas.length; i++) { places[i] = i * CARD - offset; if (places[i] > -CARD && places[i] < VIEW) show(slot++, teas[i].name, places[i], sizes[i]); } hideFrom(slot);}- Line 1 makes one list of numbers,
places, once, when the page starts. AFloat64Arrayis a list of numbers with a fixed length. Writing into it makes nothing new. - Line 2 makes every tea's size labels once, also when the page starts.
- Lines 3 to 10 are the new drawShelf. Line 6 writes each tea's place into the same array, every frame.
- Line 7 shows a tea if it's on the shelf, with its labels from line 2.
Both loops visit all 3,000 teas, and both show the same cards. Only the first makes new objects as it goes. Read the table across: each row is one thing the recording measured, for each shelf.
| What the recording measured | Makes new objects | Reuses one array |
|---|---|---|
| Time making the shelf, per frame | 3.7 ms | 0.1 ms |
| Clean-ups during the five glides | 79 | 0 |
| Time cleaning up, in all | 58.8 ms | 0.0 ms |
| Script of the middle frame | 15.9 ms | 12.0 ms |
| Frames dropped | 11 | 0 |
| New pictures on Ben's screen | 178 | 189 |
| The JS heap during the glides | 1.3 to 5.2 MB | 1.6 to 1.9 MB |
| Ben's slowest tap, as INP counts it | 64 ms | 56 ms |
Look at the last row. Ben's taps were quick on both shelves, well under the 200 ms line for a good INP, Interaction to Next Paint (lesson 26.1). INP times each tap only until the next frame. The dropped frames came later in each glide, so INP can't see them. To catch a jerky slide, look at the frames.
Making the objects cost more time than cleaning them up: about 3.7 ms in every frame, against less than a millisecond about every other frame. V8's team says that short-lived objects are cheap to clean up, because a Minor GC only pays for the objects that survive. The recording agrees. The trouble is the making, and how often the clean-ups come.
The heap tells the same story. Here is its size during Ben's second glide, each time Chrome noted it:
The first shelf fills a few megabytes in two or three frames. Then a clean-up empties it. The line looks like the teeth of a saw. The second shelf barely moves. Why it matters: you can see this in DevTools. Tick Memory in the Performance panel. A JS heap line that climbs and drops again and again during a slide means code is making lots of objects that die young.
A few habits for code that runs on every frame, every scroll or every key:
- Make lists and labels once, outside the loop, and reuse them.
- Write into an array you already have, instead of building a new one with
maporfilter. - Look up only what is on screen: the five teas you show, not all 3,000. Lesson 28.3 does this for long lists.
- Measure first. Code that runs once, on a click, rarely needs this care.
Try both ways in your own browser. The lab runs the two functions on 3,000 teas for 60 frames:
// Makes new objects every frame
function drawMakingNew(offset, show) {
const cards = teas.map((tea, i) => ({ name: tea.name, x: i * CARD - offset, sizes: sizesOf(tea) }));
let slot = 0;
for (const card of cards.filter((card) => card.x > -CARD && card.x < VIEW)) show(slot++, card.name, card.x, card.sizes[1]);
return slot;
}
// Reuses one array and labels, made once
const places = new Float64Array(teas.length);
const sizes = teas.map(sizesOf);
function drawReusing(offset, show) {
let slot = 0;
for (let i = 0; i < teas.length; i++) {
places[i] = i * CARD - offset;
if (places[i] > -CARD && places[i] < VIEW) show(slot++, teas[i].name, places[i], sizes[i][1]);
}
return slot;
}Both ways look at all 3,000 teas on every frame and show the same cards. Only one makes new objects as it goes.
Phones with little memory
Ben's phone has much less memory than a laptop. That changes two things.
First, clean-ups come more often. V8 sizes the young generation by how much memory the device has. The young generation has two halves. On a computer, each half can grow to 32 MB. On Android phones with less than 8 GB of memory, it stops at 8 MB or less. The same code fills a smaller space sooner, so it cleans up more often. The recording gave V8 the same 8 MB limit, with V8's own flag, --max-semi-space-size.
Second, the phone closes things to free memory. Android runs a low memory killer. When memory runs short, it ends the processes that matter least. Apps you aren't looking at go first. A Chrome tab runs in a process too, so a tab in the background can be closed this way. Android's own guide says that the less memory an app holds in the background, the better its chances to survive.
Four flatmates share one small fridge. When it's full and someone brings new shopping, the old leftovers at the back go in the bin. Nobody asks whose they were. In a bigger fridge, they would still be there.
- In real life: The fridge
- In JavaScript: The phone's memory
- In real life: The flatmates
- In JavaScript: Apps and tabs
- In real life: Old leftovers at the back
- In JavaScript: Tabs and apps in the background
- In real life: Leftovers going in the bin
- In JavaScript: A tab closed, to be loaded again later
Can a page tell that it's on such a phone? navigator.deviceMemory gives the device's memory in GB. It's rounded, so it says less about who you are. Chrome on Android says 1, 2, 4 or 8. Chrome on a computer says 2 to 32.
const memory = navigator.deviceMemory ?? 4; // in GB, rounded
const teasPerShelf = memory <= 2 ? 300 : 3000;
console.log(`This shelf will hold ${teasPerShelf} teas.`);- Line 1 reads the memory, or uses 4 if the browser doesn't say. Firefox and Safari don't.
- Line 2 picks a smaller shelf on a phone with 2 GB or less.
- Line 3 prints how many teas the shelf will hold.
One warning about testing. DevTools can slow the CPU, but it can't shrink the memory. With the CPU 4 times slower, the recorded page still read navigator.deviceMemory as 16, the computer's own memory. Why it matters: to see memory trouble as Ben does, test on a real cheap phone.
When the browser throws a tab away
Back to Ben's cart. When he switched to WhatsApp, his tab went into the background. The Page Lifecycle API names the states a page goes through there:
- Hidden: not on screen. Its code can still run. The page hears
visibilitychange. - Frozen: the browser stops its timers and tasks, to save power. The page hears
freezefirst. - Discarded: the browser throws the page away, to free memory. The tab and its title stay, but the page is gone. Nothing runs, so the page hears nothing.
When Ben opens the tab again, the browser loads the page from the start. The new page can read document.wasDiscarded. It's true when the page before it was thrown away.
You work at a library desk with your notes spread out. You step out for a call. The library is busy, so the staff clear your desk for someone else, and leave a slip with your name. When you come back, the desk is empty. Only what you put in your locker is still there.
- In real life: Your notes on the desk
- In JavaScript: The page and its data, in memory
- In real life: Stepping out for a call
- In JavaScript: The tab going into the background
- In real life: Staff clearing the desk
- In JavaScript: The browser discarding the page
- In real life: The slip with your name
- In JavaScript: The tab's title, and document.wasDiscarded
- In real life: Your locker
- In JavaScript: sessionStorage
The course did this to Ben's tab for real. Ben added three teas. Then another tab came to the front, like his WhatsApp. Chrome's test page, chrome://discards, froze his tab, then threw it away. Then the tab loaded again. There were two versions of the cart page. The first keeps the cart in a variable only: const cart = [];. The second also saves it:
const cart = JSON.parse(sessionStorage.getItem("cart") ?? "[]");document.addEventListener("visibilitychange", () => { if (document.visibilityState === "hidden") { sessionStorage.setItem("cart", JSON.stringify(cart)); }});- Line 1 reads the saved cart when the page starts.
sessionStoragekeeps text for this tab, even when the page is thrown away. If nothing was saved,?? "[]"gives an empty list. - Line 2 listens for the page being hidden or shown.
- Lines 3 and 4 save the cart, as text, each time the page is hidden.
Here is what Ben saw, from the recording:
The page wrote down each event it heard. First visibilitychange, then freeze. At the discard, nothing. When the tab loaded again, document.wasDiscarded was true. The first page came back with Cart: 0. The second came back with Cart: 3: Mint tea, Earl Grey, Jasmine.
Why it matters: there's no event to catch at the moment of a discard. So save when the page is hidden. Chrome's team warns not to wait for pagehide, beforeunload or unload, because they don't fire on a discard.
if (document.wasDiscarded) {
// Chrome threw this page away while it was hidden, then loaded it again
console.log("Welcome back. Your cart was saved.");
}- Line 1 checks
document.wasDiscardedonce, when the page starts. - Line 2 is a comment. It says what a true value means.
- Line 3 tells Ben his cart is safe. A real page could also send the fact to its analytics, to count how often it happens.
Chrome on a computer is careful about what it throws away. After Ben typed a note for the gift card, chrome://discards gave two reasons not to discard his tab: Tab has form interactions and The user has edited the tab's content. The discard didn't happen. But a phone's low memory killer doesn't read forms. Save typed text too.
Memory Saver is a Chrome setting on computers that throws away tabs you haven't used for a while. Its help page lists partly filled forms among the things that keep a tab active.
Try saving and starting again in this tab:
- In the cart
- Nothing yet
- Saved the last time this tab was hidden
- Nothing
- document.wasDiscarded in this tab
- …
- navigator.deviceMemory in this tab
- …
Add teas, switch to another tab and back, then press Start the page again. This lab can't make Chrome throw the tab away. It does what a page loaded again would do: it starts from nothing and reads back only what was saved.
When a page runs out of memory
A discard throws away a page you weren't looking at. Running out of memory can end the page you are looking at.
Each tab may only use so much memory. If a page keeps growing past that, its tab crashes, like a suitcase that bursts when you keep pushing more in. Chrome then shows its crash page: "Aw, Snap! Something went wrong while displaying this webpage." If it happens again, Chrome can say: "Try closing other tabs or programs to free up memory."
The course didn't crash a tab on purpose. The words above come from Chrome's own source. The usual cause is a page that grows and never lets go: a list that only gets longer, a cache with no limit, or listeners that keep old elements alive. That's a leak, and the next lesson finds leaks with heap snapshots. Long before a crash, a growing page gets slower. Each Major GC has more to look through, and the phone closes more of the things around it.
- Load data a page at a time, not a whole year of orders at once.
- Drop what the visitor can't see any more, so the GC can free it.
- Measure memory from real visits. Chrome has
performance.measureUserAgentSpecificMemory(), and an older, non-standardperformance.memory. Lesson 29.3 covers both.
Which memory problem is it?
Each report below comes from a real kind of memory trouble. Sort it by its cause:
- A trace shows a Minor GC inside many frames of a scroll
- The JS heap line climbs and drops again and again during an animation
- After taking a photo, a phone visitor comes back to a page that loads from the top
- Analytics show document.wasDiscarded is true for some page views
- A dashboard left open all day ends on an Aw, Snap! page
- Loading a whole year of orders into one array crashes the tab on cheap phones
Sort each report by what causes it.
Common mix-ups
- "Garbage collection only runs when the page is idle." No. V8 tries to use quiet moments, but 74 of the 79 clean-ups ran in the middle of the shelf's drawing code.
- "Objects that die young are free." No. Each clean-up was short, but making the objects took about 3.7 ms a frame, and the clean-ups came about every other frame.
- "A slow CPU in DevTools shows me a phone's memory." No. It only slows the CPU. The page still saw 16 GB.
- "A good INP means smooth animation." No. INP times each tap until the next frame. Both shelves had a good INP, and one still dropped 11 frames.
- "pagehide will save the cart before a discard." No. It doesn't fire on a discard. Save on
visibilitychange. - "A discarded tab is a closed tab." No. Its title stays in the tab strip. When you go back, the page loads again, with
document.wasDiscardedset to true.
Practice exercises
Answer first, then use the hints.
This tiny shelf has two teas. How many card objects does it make in 36 frames?
const teas = [{ name: "Mint tea", price: 4.5 }, { name: "Green tea", price: 4 }];
let made = 0;
function drawShelf(offset) {
const cards = teas.map((tea, i) => {
made++;
return { name: tea.name, x: i * 124 - offset };
});
return cards.filter((card) => card.x < 388).length;
}
for (let frame = 0; frame < 36; frame++) drawShelf(frame * 10);
console.log(made);72. Two new cards in each of 36 frames. Ben's real shelf has 3,000 teas, so it makes 3,000 cards a frame.
This function rounds memory the way Chrome does, with Chrome's limits for Android. What does it print for a phone with 3 GB?
function deviceMemory(megabytes, lowest, highest) {
let power = 0;
for (let rest = megabytes; rest > 1; rest = Math.floor(rest / 2)) power++;
const lower = 2 ** power, upper = 2 ** (power + 1);
const near = megabytes - lower <= upper - megabytes ? lower : upper;
return Math.min(highest, Math.max(lowest, near / 1024));
}
console.log(deviceMemory(3072, 1, 8)); // an Android phone with 3 GB2. 3 GB rounds down to 2 GB, so a page sees a 3 GB phone as a 2 GB phone.
This page saves the cart in pagehide. What does Ben see when his tab loads again?
const storage = new Map(); // stands in for sessionStorage
const heard = ["visibilitychange", "freeze"]; // what Ben's tab heard before Chrome discarded it
let cart = ["Mint tea", "Earl Grey", "Jasmine"];
for (const event of heard) {
if (event === "pagehide") storage.set("cart", JSON.stringify(cart));
}
cart = JSON.parse(storage.get("cart") ?? "[]"); // Chrome loads the tab again
console.log(`Cart: ${cart.length}`);Cart: 0. The page saves only in pagehide, which doesn't fire on a discard. Save when visibilitychange says the page is hidden.
In the five-glides playground, how many clean-ups did the makes-new shelf run in glide 5?
9. The last glide had fewer clean-ups than the others. Even so, it dropped 1 frame on the shelf that makes new objects.
Open the course's trace of the makes-new shelf in Chrome's DevTools. Click a Minor GC inside one of the glides. Which JavaScript function was running when it started?
glideStep, the function each frame of the glide runs. It calls drawShelf, and the Minor GC started while drawShelf was making cards. The trace has no samples of the code itself, so DevTools shows the outer function.
Chrome's help page for Memory Saver lists things that keep a tab from being thrown away. Name one, other than a partly filled form.
Any of: active audio or video, screen share, page notifications, active downloads, pinned tabs, or connected USB or Bluetooth devices.
Check your understanding
For each question, ask which cost it's about: making objects, cleaning them up, or a tab thrown away.
Question 1 of 7What does this cart code print after Chrome discards the tab and loads it again?
Read the code, then predictconst storage = new Map(); // stands in for sessionStorage let cart = ["Mint tea", "Earl Grey", "Jasmine"]; storage.set("cart", JSON.stringify(cart)); // visibilitychange: the page is hidden cart = JSON.parse(storage.get("cart") ?? "[]"); // Chrome discarded the tab, then loaded it again console.log(`Cart: ${cart.length}`);Choose an answer to see the explanation.
Question 2 of 7Which event does a page receive at the moment Chrome discards its tab?
Choose an answer to see the explanation.
Question 3 of 7On the makes-new shelf, each clean-up took about a millisecond. Why did frames still get dropped?
Choose an answer to see the explanation.
Question 4 of 7Both shelves had a good INP in the recording, yet one stuttered. Why didn't INP show the stutter?
Choose an answer to see the explanation.
Question 5 of 7Why does the same code clean up more often on a cheap Android phone?
Choose an answer to see the explanation.
Question 6 of 7Does slowing the CPU in DevTools also give the page a phone's small memory?
Choose an answer to see the explanation.
Question 7 of 7In the recording, Ben typed a note on the cart page. Why wouldn't Chrome discard that tab?
Choose an answer to see the explanation.
Key takeaways
- Every new object costs twice: once to make it, and once to clean it up on the main thread.
- One clean-up is short. Many clean-ups inside full frames drop frames.
- In code that runs every frame, reuse arrays and make labels once.
- Phones with little memory clean up more often, and close tabs in the background.
- Save the visitor's work on
visibilitychange, and readdocument.wasDiscardedwhen the page starts. - A page that keeps growing ends on a crash page.
Remember the one-liner.
Make less in busy code, and save what the visitor would miss.
Sources
The facts on this page come from these docs, specs and source files, and from the course's recordings:
- Trash talk: the Orinoco garbage collector, V8.
- Getting garbage collection for free, V8.
- Concurrent marking in V8, V8.
- Fix memory problems, Chrome DevTools docs.
- heap.cc: how big V8 lets the young generation grow, V8 source, as Chrome 157 ships it.
- Overview of memory management, Android Developers.
- Low memory killer daemon, Android Open Source Project.
- Device Memory, W3C.
- approximated_device_memory.cc: the values Chrome reports, Chromium source.
- Page Lifecycle API, Chrome for Developers.
- Memory Saver and Energy Saver, Chrome for Developers.
- Personalize Chrome performance, Google Chrome Help.
- discard_eligibility_policy.cc: which tabs Chrome may discard, Chromium source.
- sad_tab.cc: when Chrome shows its crash page, and which words, Chromium source.
- new_or_sad_tab_strings.grdp: the crashed tab's words, Chromium source.
Coming next: Finding leaks in web apps. A page that only grows, and how to find what keeps it growing.