“Your app crashes my browser” talk

June 27th, 2024. Tagged: JavaScript

Today I gave a talk on memory leaks in web apps at the wonderful dotJS conference in Paris' Folies Bergère theater. It was only 5 minutes, so not much time for links and such. Here's more or less what I said including the links.


Bonjour à toutes et à tous, who's excited about... Memory Leaks!?

How many of you have seen this crash? It says "Aw Snap! Out of memory." Rarely, right? We have powerful computers we use for development.
And do you think anyone ever sees this while using your app? Never! How dare you?
You may be surprised.


Couple years ago Nolan Lawson used his tool appropriately named fuite to check the top 10 most popular SPAs.
What do you know, 10 out of 10 apps had leaks.
And these are "the top" 10 apps with good developers working on them.
So it's not that we are lazy or sloppy, it's just that leaks are easy to create.


I while ago I was working at a famous social media site. And we figured we have a problem. We crash people's browsers.
Because we leak memory.
Where do we leak? Who knows, it's a big project.
So what's the solution when we didn't have good debugging tools?

After 15 or so soft navigations, just fully reload the page.
Give the browser chance to start over.
Turn the SPA into an MPA just for a moment.
How embarrassing!


Today things are very different. Today we have tools.


The first step in addressing a problem is admitting you have a problem, right?
How do you know you leak memory?
Check out the... Reporting API
It allows you get data back from the browser, when your users see OOM crashes in real life.
You can stop guessing and get an idea of the real problems.


And let's say you discover that you do leak memory? What to do about it?

Option A: call a friend. This is a person who knows all the secrets of the Universe and can
dive into your app and unearth... The leak!
And then you fix it. And then all is fine. No.
The thing is there's usually not a single leak. And if you fix The One, the next is just around
the corner



Option B: take memory snapshots and look at them. Here's the sequence.

Load your app, make the browser perform garbage collection to free memory and take snapshot 1
Then “navigate” or interact in some way with the app, garbage collect again, and take snapshot 2
Finally go back to the old state, GC, take snapshot number 3


Then you look at what changed between snapshots 1 and 3 and find any JavaScript or DOM objects that are retained but shouldn't be.
If this sounds hard to you, that's because it often is. If only there was a tool that can help...


Enter Memlab. It's a command-line open-source tool by Facebook.
It's been used to plug memory leaks including some in React itself.

It does the three stages: initial load, interaction, and back to the initial state.

Here is an example, these bars represent memory consumption.
Here not only memory is lost on interaction. But even more when going back.

The main goal of Memlab is to do an intelligent diff of the snapshots and point to the source of the leak.
Here it reports that 1 leak was found that leaks a 1000 similar objects and gives you the path to the first one of those objects: a DIV element.


This is a real example of a popular Maps app.
You load the page (bar 1), click a button to show hotels nearby (bar 2) and then unclick the thing.
And you see, memory was leaked again.


Memlab uses Puppeteer to drive the browser and needs a so-called Scenario file.
This is simply a javascript module that implements three functions:

  • function url(){} for the initial load
  • function action(){} for the interaction
  • function back(){} to go back to the initial state

If you rarely use Puppeteer it can be a bit of a curve to get off the ground.
But to get you from 0 to 1, I've published a Chrome extension:
Memlab Scenario Recorder (webstore, code). It allows you to just click around and have the scenario
javascript file generated for you.


Alright, let's quickly touch on the source of leaks.
In general they are caused by objects that are no longer needed but some reference, some variable is pointing to them.

The solution is to just assign null to that variable.
This signals to the GC that the object is no longer needed and can be safely cleaned up.


In web applications, we sometimes have event listeners
that keep on listening even after the DOM element is no longer around.
Let's see an example using a React class.


Nothing out of the ordinary, right?
Well, when this component is removed from the DOM, the event listener is not.
And that's a memory leak.


The fix? Clean up when we're done.
In this case it means remove the event listener using componentWillUnmount() or whatever API your framework offers.


To wrap up: memory leaks...
I don't mean to sound paranoid, but they're everywhere, man.
As you can see leaks are hard to find, easy to fix.


Use the tools we have available today and find your first leak. And make your users, if not happy (you can't guarantee happiness), at least less frustrated.


Video by Eunjae Lee

Comments? Find me on BlueSky, Mastodon, LinkedIn, Threads, Twitter