Joao Santos
Jul 21 · 8 min read

If you ever had the pleasure of writing a single page application or other non trivial web application you no doubt asked yourself at some point, does my app leak memory? The answer is almost guaranteed to be yes. It’s easy to forget to remove an event subscription and ending up retaining objects. Most of the time it’s not a big deal, what’s a few more KB of ram nowadays?

A few kb here and there will sometimes add up into several MB or even hundreds of MB and then the infamous “Aw, snap!” screen will show up. It’s time to debug memory leaks, but before we can start debugging memory leaks, we should first understand what even is a memory leak?

How memory generally works

In programing when you allocate memory to store data in the heap that memory needs to be freed again before the OS can use it for something else. While the memory is allocated it is reserved for the application that allocated it.

In garbage collected languages such as javascript, memory allocation and deallocation is generally done behind the scenes. Memory is allocated whenever a new object is created and deallocated when the garbage collector detects that all references to that object are gone. An object reference is a variable (pointer) that allows access to that object.

How memory works in javascript

In javascript the memory is managed as a network of objects, each node is an object however these objects are not the just the javascript objects but the objects created by the javascript runtime as well which includes scopes.

For an object to be garbage collected there has to be no more path from any active scope to the object in question. This is achieved by nulling references (assigning another value to the reference) or in case of local variables, when the scope is exited

A memory leak occurs when an object that you thought would be garbage collected isn’t collected because there is still at least 1 path in the network that allows to reach the object whether you know it or not.

Note that the difference between memory leak and normal behavior is the intent! All patterns of memory leaks only apply if you do not wish for the object to stay in memory

Some examples of memory leaks

Globals

Since whether or not an object is accessible is what decides garbage collection, all global objects by definition never get collected. This does not just refer to window (global on nodejs) but also to all variables exported by javascript modules.

Example javascript module:

class SomeClass { … }

export const permanentInstance = new SomeClass();

permanent Instance will never be collected because it’s a global object due to the fact that it can be imported anywhere. It is important to note that most of the time when you have a significant memory leak the issue is not a single global instance but the consequences of global instances keeping other stuff around since everything referenced by a global instance is also never garbage collected.

Observer pattern (Events)

The observer pattern is very popular in javascript but it is also prone to cause memory leaks: Every event subscription is a potential memory leak. There is a reason every event subscription API comes with an unsubscribe function: When you create a subscription, the callback is passed by reference to the event source and stored there until unsubscribe is called or until the event source is garbage collected itself.

What this means: Whenever you subscribe, if there is no matching unsubscribe the closure of the callback will stick around for as long as the event source exists. This closure leaks all variables that the callback function uses internally that come from outside the function

Example:

const someData = “hello world”;

someButton = document.getElementById(“button”)

someButton.addEventListener(‘click’, () => console.log(someData))

As long as the button exists in the page and the event listener is not removed, the string hello world will remain in memory due to the closure created by the arrow function passed to the click event. I hope that is obvious by now that this is because someData is accessible and it would make no sense for it to be garbage collected since it’s obviously needed.

How to debug memory leaks

Memory leaks can be very hard to debug, often you don’t know what you are looking for and sometimes the leaks are static making detection very difficult because your best friend in debugging memory leaks are changes in the memory over time.

Spotting a leak

When you click around in your web application or sometimes even just by waiting you can check your memory at regular intervals and see the trend. All applications that create a decent amount of objects will see the memory go up over time. This is not necessarily a leak! Garbage collection is expensive on the CPU and therefore the garbage collector does not do a full sweep of the memory all the time. You can view a graph of memory use over time by using the performance tab in chrome’s inspector starting to monitor, perform some actions and then stop the recording

This looks like a typical graph, memory goes up, garbage collector does a sweep and the memory goes down to roughly where it started at.

The problem is if it does not go down to roughly where it started but starts to drift higher and higher as you perform actions on the page.

Reading a memory snapshot

This section refers specifically to the chrome inspector. Memory snapshots could be represented differently by other debuggers.

In the memory tab of chrome you have the option to take a heap snapshot. This will create a large json report of the whole memory network and display it as a list of nodes.

The list is grouped by node type, when expanded you see each node in the memory network of the given type. Distance stands for how many nodes do you have to traverse at least from an active scope to get to the object in the memory network. Shallow size is the amount of memory the object itself uses, retained size is the amount of memory an object is keeping from being garbage collected.

You will find some nodes that are javascript engine internals like compiled code, system etc, you do not need to worry about those since you can assume that potential leaks come from your code not from the browser.

When you click on a node you get a list of “retainers” which is one path in the network that keeps the object from being collected. This is problematic however because if you have a deeply interconnected data structure like a tree with parent references there are many paths and chrome will chose one to show you, it could show you a path that has an infinite recursion of parent child referencing back and forth and never actually show the root that is retaining your object

So your goal when you find a suspect object is to see what retains it and decide based on that where the reference is that you don’t expect. To help with this you can hover on a node or click it and type $0 in the console to get a reference to that node in memory, this can help understand what the object is that is retaining your object

Debugging leaks

You are unlikely to be able to find every last leak especially the small ones, but like in optimization, start with the worst offender and work your way down until you are satisfied with the memory use.

If you have a hunch what objects could be leaking and if those objects are class instances then you are in luck. Go to the memory tab of the chrome inspector choose heap snapshot click start and once it is done type the name of your class in the search bar. If you find matches, you can see the number of matches and compare to how many instances you’d expect to be around for the state your page is in. For example if you have 10 instances of “Widget” but there are only supposed to be 2 widgets, you have yourself a prime suspect.

If you don’t know what to look for, one strategy is to think of the major class instances in your application that are created and destroyed over time. Like widgets, editors, ui components etc. Then enter and exit a state where those objects are created repeatedly and then take a heap snapshot and compare to number of instances in memory to the expected amount.

If you still don’t get any good results another strategy that does not involve class instances is the 3 snapshot technique:

  1. Put your page in the desired state and press F5 to get a clean state. Once the page is loaded, take a heap snapshot.
  2. Perform some actions (NO REFRESH!) and bring the page roughly back to the same state it was in at the end of step 1. Take another heap snapshot

3. Try to change the page’s state as much as possible but make sure no refresh occurs because refresh will wipe the memory clean wasting our efforts. Once the page has a completely different state take a third heap snapshot

You can now click the third snapshot and next to the class filter select Objects allocated between snapshot 1 and snapshot 2

This will show all objects that were created between snapshot 1 and 2 that are still around in snapshot 3! This is why it’s so important to change the state as much as possible in step 3: to ensure as many objects as possible were collected. This exposes a much smaller list of things that stuck around and you should be able to decide on case by case basis if that is expected or not.

In conclusion, classes are you friends when it comes to memory leaks because anonymous objects blend in the crowd making it hard to spot unexpected accumulation of objects.

The Startup

Medium's largest active publication, followed by +514K people. Follow to join our community.

Joao Santos

Written by

The Startup

Medium's largest active publication, followed by +514K people. Follow to join our community.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade