JavaScript Memory Leaks

Introduction and Overview
Basarat Ali Syed introduces the scope of the talk, focusing on JavaScript memory management, rather than React. He outlines the session's structure, identifying three key topics: JavaScript garbage collection, memory allocation and release, and weak data structures. He engages the audience with a quick poll on their familiarity with WeakMap, WeakSet, and WeakRef, noting that these concepts will be explored throughout the presentation.
Understanding Garbage Collection and Memory Allocation
The speaker explains memory allocation in JavaScript, using variables as examples of how memory is allocated automatically by the garbage collector. He clarifies misconceptions about the size of primitives and the allocation of memory for arrays and objects. The focus is on the role of the garbage collector in deallocating memory when variables go out of scope or are no longer referenced. Basarat describes a demo involving NodeJS to illustrate memory usage monitoring and forced garbage collection.
Demo: Manual Memory Management in JavaScript
The speaker conducts a demo to show how memory can be managed manually in NodeJS, utilizing `process.memoryUsage` to monitor memory allocation and employing the garbage collector function `gc` to demonstrate memory freeing. He creates a class called Heavy to showcase how significant memory usage is handled in JavaScript, using a few iterations of memory allocation and deallocation in NodeJS to illustrate the behavior.
Memory Management Strategies
Basarat explains strategies for managing memory effectively, including explicit deallocation by dereferencing objects and how JavaScript’s garbage collector optimizes memory usage without explicit intervention. He mentions different approaches to managing references and provides examples of how to allow the garbage collector to reclaim memory. The session also highlights differences between server-side (NodeJS) and client-side (browsers) memory management.
Introduction to Memory Leaks
The speaker introduces the concept of memory leaks, drawing an analogy with water leaks. He presents examples to demonstrate how unintentional retention of memory references leads to inefficiencies, and showcases techniques to modify code to avoid memory leaks, like variable reassignment and the use of block scopes.
Working with Closures
Closures are explained as a potential source of memory retention due to their nature of retaining references to outer variables. Basarat provides examples to show how closures can inadvertently lead to memory leaks if not managed properly, and advises on methods to ensure memory is freed up when closures are used extensively.
Weak References and Their Uses
The speaker discusses weak references, highlighting how they differ from strong references by allowing garbage collection even if the references are still in use. He provides insights into when and why you might use WeakRef to hint to the garbage collector that an object can be de-referenced if necessary.
Utilizing WeakMaps and WeakSets
Basarat explains WeakMaps and WeakSets, contrasting them against regular Maps and Sets. He emphasizes how these structures do not prevent garbage collection of objects and do not hold strong references. This section includes examples of use cases in frameworks like React, where these structures help manage DOM-related memory efficiently without causing memory leaks.
Conclusion and Advanced Topics
Wrapping up the talk, Basarat explores advanced memory management tactics, clarifying nuances of reference management in JavaScript. He answers audience questions and briefly outlines the implications of using weak data structures in complex JavaScript applications. The session concludes with a recap on preventing memory leaks and efficiently managing resources in JavaScript environments.
Thank you, Ben.
So interestingly this talk is not going to have anything specific about React.
It's more about the back of the front end, I think.
The JavaScript in general, it'll have three sections.
We're going to talk about the JavaScript garbage collection and how you allocate and allocate memory in JavaScript.
We're going to talk about memory release as well.
And finally, weak data structures as well.
Can you raise your hands if you have used WeakMap?
WeakSet?
WeakRef.
Cool.
Very few people.
Mark is one of them.
He works on Remix, so I'm not surprised.
Is it used in Remix or one of those things or, so WeakSet that is actually used in React as well.
We'll look at that in the course of this presentation.
So first thing, transcript, garbage collection.
I have a question for you.
How do you allocate memory in JavaScript?
Sorry, what?
Const.
Yeah.
So like whenever you create a variable, you're essentially going to allocate memory.
Let will do it as well while we'll do it.
That's not specific to the, like creating a function will also allocate memory.
You just write code and the garbage collector will allocate memory for you.
Another, like you might think that a number is not necessarily big in memory, but of course you can have an array of numbers or strings or any of the other primitives.
JavaScript is going to allocate memory in the back for you.
How do you free up memory in JavaScript?
Any guesses?
Yeah, so like you don't necessarily have to think about it.
Whenever you stop using a variable, the garbage collector will jump in and it will deallocate that memory for you.
For example, over here, these variables are declared with const in a block scope.
And when the scope exits, these variables are no longer going to be referenced anywhere.
You cannot use them in, any meaningful way.
The garbage collector will jump in and if there is a need to free up some memory, it will allocate that memory for you.
Another thing that you can do is, for example, just stop referencing the object or the primitive or whatever in your code, and the garbage collector knows that.
Okay?
And I can free up that array or that number.
Let's do a demo on this.
So within the browsers.
Within the browsers, you cannot necessarily, access how much memory is being used or do any of the server side kind of things.
Like you cannot access the file system, right?
So you also cannot access the machine memory and find out how much memory is there kind of thing.
But within NodeJS this is, it's a trusted environment.
You can do that.
And the way to do that is using process, memory usage.
It tells you a few parameters and the memory that you work with your variables is on the heap, like that's where it goes.
So heap use tells you how much memory your code is using.
It's in, bytes.
So I'm converting into megabytes over here.
And let's just run it to look at how much memory, freshly booted NodeJS on my machine is going to use.
And it's using 84 mb.
Do you think that's a lot or.
That's a lot.
Yeah, I would agree with that.
That is because of the NodeJS runtime.
It does allocate a few things that it needs in order to function.
But you can free that up as well.
So within NodeJS you can actually access the garbage collector and it just has one function gc.
You can see that on screen over here, which basically asks, requests the garbage collector to collect as much memory as it can.
So if I look at the heap used before and I look at it after, and of course again, convert into megabytes, I can see how much garbage is going to get freed up garbage being memory.
So if I collect garbage over here, so out of that 84, I immediately shaved off like 15 MB.
Yeah, so that's the garbage collector.
Let's write a function that's going to consume a lot of memory.
I am going to, that's fine.
Is that still visible back there?
Cool.
Perfect.
I have this class called Heavy that has an array, which is going to be a large array of, hundred thousand single characters.
Hopefully a character is a byte kind of thing.
Don't have to do the math yourself.
This.
The exact value of the memory consumed is not the core of this presentation anyways.
Consume is going to create an array of these heavy objects, 2,500 of them, each being a hundred thousand characters.
So let's consume an amount of memory and see what it looks like.
So I look at the memory before, then I consume, and then I look at the memory after.
Interestingly, over here, I'm pretty sure that the garbage collector has run.
In fact, I could even do over here and, collect garbage beforehand.
And you can see it's like a thousand mb.
So that's all good.
So now we have utilities that can collect garbage, can show you how much memory is being consumed right now, and you can, yeah, this function that can consume a lot of memory.
So I have this thing over here where.
I am gonna collect the garbage just to get a clean slate to start from.
Then I'm going to look at how much memory is being used.
Then I'm going to consume using that consume function that you just saw, which is going to consume around a thousand MB. Then I'm going to log out the memory used afterwards.
Then I'm going to collect garbage again, and then I'm going to do this a few times.
Now by default, NodeJS has four GB.
So a thousand thousand four times of worth of memory.
Is this going to work fine?
Am I going to get a memory exhaustion over here?
I'm only doing it three times.
It shouldn't matter.
Plus, I'm also collecting garbage every single time I'm allocating memory.
So that memory should get freed up and we can, we should be able to see that between the thing.
And you can see that before I create this, consume a lot of memory.
There's this much, but after I consume, it gets big.
But when I call the force the garbage collector, hey, clear up all memory that you can, it can clear that up.
So this consume function, which is just creating an array somewhere and we are not referencing it anymore once that function returns, that memory can be freed up that's, this code can, you can do this as many times as you want.
This was with me calling the collect garbage every single time after I allocate.
This is the same example, without me calling collect garbage.
I'm just consuming and looking at the memory usage as it goes.
Doing it a bunch of times.
Let's run this and you can see that it stays steady even though I'm not calling collect garbage.
The runtime has figured out, Hey, I'm, using one GB over here.
That's a lot for this particular, whatever this function did, I'm going to free it up before I run the next score.
And that's ensuring that it's staying at around a thousand.
So that's a good sign.
You don't necessarily have to worry about memory.
'cause when you call functions, they do some stuff, they go away.
That goes away.
It's all good.
That's why we don't have the collect garbage function explicitly in the browser.
But what happens if I keep the result?
So if you look at the consume function.
It is going to return the array that it's creating of those 2,500 heavy objects.
So if I were to keep them around, can, the garbage collector jump in and free up that memory?
Take a wild guess, I think that was an obvious question.
That's why perhaps you, is anybody confused about any of this?
If you have questions, feel free to ask at any point in time.
But yeah, so because I'm keeping, a reference to those variables are still in scope.
That memory cannot be freed up anymore.
So it'll keep going up.
So it went to a thousand, went to 2000, and then it struggled and okay, I can't, garbage collect anymore.
Allocation, failure scavenge might not succeed.
So it's telling you some of the internals of the JavaScript garbage collector, it's a mark and sweep.
As far as I understand, we are not going to discuss the internals of how the garbage collect works.
It's there for you.
It keeps you from, running out of memory if you let go of your variables.
But over here I am holding onto those variables so it cannot let go of that memory.
So it's okay, I'm going to just crash.
Any questions about that?
Go.
Let's look at the next thing.
Let's, do the same code within the browser.
So we've been running it within the NodeJS, let's, run this within the browser as well.
Just as a demo, I'm going to copy all of this code, go into a browser instance and paste it into the console.
So what I'm doing over here is I have that heavy object.
I'm consuming creating 2,500 of those heavy objects, and I'm storing the reference to those variables.
So I should run out of memory.
This is just a demo of what that would look like in the browser.
Forcefully it is going to ask me to, even if I ask it to not pause on any exception whatsoever, it is going to pause on this one 'cause it's like dying.
It's not an unhandled exception that 'cause you cannot handle, memory exhaustion.
What you can do is you can listen to it within NodeJS as an example within process dot exit.
But in the browser it's okay, the tab is gonna die.
So that's why it's okay, I'm dying here.
Are you sure you want me to proceed?
I am sure I want you to proceed.
And of course, that's what you look what it looks like.
If you don't have the dev tools open, this is what you'll see.
So it can be a million reasons why you might see something like that.
One of them might be that it, that tab has run out of memory.
But what happens if I free up the memory?
So one way that we looked at how we can feed up the memory is just not store that I could delete this trash variable entirely.
And that would mean that array is only created within the consume function.
Once it comes out, that memory can be freed up, but another way I can do it is I can still use that result in any way that I want to and then just assign that variable to nul, or undefined or anything else honestly, as long as that particular object is no longer referenced, the garbage collect can jump in and empty up that space.
So if I run the same thing over here, paste it in, this will be fine.
Memory consumption is stable.
So like it allocates, allocates, it's fine.
Any questions about that?
Cool.
So that was a brief overview of garbage collection, how you allocate memory, deallocate memory.
The next thing that we have to talk about is memory leaks.
Anybody care to define what is a memory leak?
Take a whack at it.
Okay.
What is, how would you define a water leak?
Waste of water, right?
Like you, you have a pipe.
It's supposed to pass water through it, but it's not like it's getting wasted elsewhere.
And it's the same with the flow of your code as well.
You have objects that you're allocating, but then all of a sudden you're not allocating them anymore and they're going off to the side like a pipe leak.
So water leak, wastewater, memory leak, wastes, memory.
So let's just jump into demos.
Again, lots of demos in this talk.
This is, Demo focused.
The seeing is believing.
So memory leaks.
Here's a quiz.
We are allocating heavy objects.
We've seen this thing before.
We are creating an array of, a hundred thousand characters and 2,500 of those.
We know that it's gonna take around one gB I'm allocating that and I, every single time I allocate this thing, I want to log its length.
Cool.
That's your objective.
Do it as many times as you can.
In fact, you have to modify this code so it works without crashing.
So right now it's going to crash because I have this, another variable over here.
Then another one.
So I'm doing this again and again, and I'm keeping those variables.
How would I fix this?
Answer is obvious.
Anybody wanna just shout it out?
Yeah, perfect.
That is exactly one solution.
So after you've used that variable, you can assign it to null and that would, that array is no longer going to be referenced.
Garbage collect can jump in and free up that memory.
Alternatively what I've done over here is I've just reused the same variable and now I can do it as many times as I want and it'll be fine, because that previous array is no longer being referenced.
It doesn't matter if I assign it to null or the variable goes outta scope and whatnot, it's going to continue to work.
It'll run to completion at some point, but I think you can take my word for it, but it continues to do that.
We can jump to the next thing.
Cool.
So that works fine.
Let's talk about, another way that you might think that you can get rid of the memory.
So we know that this is a variable, right?
This is, we just have to stop referencing that array somehow.
So if I cannot access result out here, results will not work.
So this should be fine.
Raise your hand if you think that this will be fine.
I have taught you well, this should be fine.
However, in this particular case, it's not going to be fine.
And the reason is that the garbage collector is not stepping in.
It's not stepping in because it's a bit lazy.
You haven't assigned it to null.
You haven't, it's still within, it's not a function scope.
It is still within the main execution scope.
So it's yeah, I'm not gonna collect this.
It's fine.
It'll be fine.
It's oh no, I can't even do a mark and sweep anymore.
I can't free up the memory.
So it's going to run outta memory.
One solution, like of course would be to assign it to null, assign it to different variable, which we saw before.
Another one that, you can do, which I don't recommend you doing, but just to demonstrate that this is, when the garbage collector will step in is if you have a function scope, right?
So I'm, running immediately executing functions.
I'm doing the same code within functions, and this will be fine.
So this is going to continue without error.
Anybody from the V8 team want to explain why the garbage collector doesn't jump in that case, please hit me up.
But, and of course like as you mentioned, assign it to null will be another option.
So even though you think that it's.
This is just a weird trick.
If you feel like something isn't getting collected when you want it to get collected, just assign it to null or undefined or just assign that variable to something else.
Even that variable might go outta scope.
If you assign it, get, stop any direct access to that, stored, allocated memory, then that'll be fine as well.
So this is again, within a block scope, but each time I'm also assigning it to null.
So it's going to be fine.
That's that.
Let's talk about closures.
So raise your hand if you know what a closure is.
Cool.
Excellent.
And you wanna define it?
I'm joking that's not a particularly easy question.
But it, most of you know what a closure is.
What is the scope of this value here?
When can this memory for this number get deallocated in here?
So I have this counter function, which is keeping that variable and returning an object.
That has a few methods that use that value, and then I'm taking that resultant object and assigning it to Alpha when this counter function exits, of course, I cannot access like value over here directly.
That's out of scope.
That's within that function.
Is that going to, does that mean that value can be freed up now outside?
Cool.
Yep.
Perfect.
Excellent answer.
Because alpha exists, we can still get access to value because alpha is this object and this object references value.
So closures essentially prevent memory from the, from, being freed up because they close over their surrounding scope and ensure that scope survives beyond the function execution.
So yeah, that, that's the answer.
So alpha, we can access count and count can access value.
Therefore, value cannot be freed up out here.
Let's look at, another example.
So we understand that closures can hold onto their memory.
So I have this allocation heavy, I'm creating this heavy object with lots of, characters inside of it.
And I have this closure function, which takes a value and then returns the new function that closes over that value.
Remembers what value, what reference to value is and returns its length to you.
And I can run it over here.
So function one will execute and it will, so I'll allocate I return function one.
Function one will look at the length of that and log it out.
And you can see that it's crashing, right?
'cause it's, all of these functions are holding onto to that memory.
Can I rewrite this to still log out the length without running out of memory?
Yeah.
So just, that there's an next answer.
So if I reassign, fn to null, or use the same variable, which is something that I did last time.
I don't know which one I'm gonna do this time.
Yes, I'm assigning it to null reallocate the variable.
Then fn is going to, the function is going to go outta scope, therefore, whatever it was closing over, which is the result of the allocate, which is just basically this thing is the problem.
This result array, that result is going to go outta scope and it'll be fine.
So this is the same code.
I'm just between executions, I'm assigning the function to null.
Any questions about that?
[quesiton] A question, but that's regarding only the root level variables, right?
Yes.
'cause they, exist in a, windows code.
Yes.
Yep, Yes.
And basically the, a lot of our applications, they create the functions.
The garbage collector will.
[Basarat] Perfect.
Yes, a hundred percent.
So if I were to do this over here, just as you, the same code within a function, this will be fine as well.
'cause when there's function exits, the fn1 will get deallocated, it's reference to the array will get deallocated and it'll be fine.
Cool.
So that's, a, demo of, memory leaks.
Let's talk about weak references.
Whenever we've created a variable over here, we've assigned it to something using the equals operator, the assignment operator within JavaScript, and that's called a strong reference.
As long as you have a reference to that thing, you can read values off of it.
Therefore, the garbage collector cannot clear it out for you, otherwise all of a sudden you are, you assign something to an object and it becomes undefined.
That would be very weird behavior if the garbage collector were to collect it.
But we now have this thing called a weak reference, and a weak reference essentially allows you to tell the garbage collector that, okay.
It's okay if you give me undefined.
If you feel like there's memory pressure and you don't want to keep a reference to that thing anymore.
Let's do a demo.
So I'll just close all of the tabs 'cause they're getting a bit too much.
Let's look at weak references.
This is the WeakRef API.
So we know how we can assign a reference using the assignment operator, right?
As I assign an object to ref, and therefore I can access ref dot X ref dot Y because that's the properties that exists on X. There's this WeakRef data structure within the JavaScript now, which takes an object or any of the non primitive types.
Sorry, I was just curious what that was.
I don't know why I'm connecting to that.
WeakRef is, takes an object and then you can deref it, so it's, it takes something that can be a reference in JavaScript.
Just, primitives aren't references as a separate topic we could talk about, but.
Anything that, that is a reference that we can pass into WeakRef, an object is a reference.
And then you can deref it to get access to that object if it hasn't been garbage collected.
So we're here.
If I run this, X and Y are going to be 10 10 for the strong reference as well as for the weak ref.
'cause there's no memory pressure here.
If the garbage collect is most likely not going to jump in, in between, but when it will collect, the weak referenced data.
So X and Y, basically it'll become drefed, will become undefined.
Let's look at that in the browser.
Cool.
So I have this, I'm creating this heavy array, create heavy, and I'm storing direct references to it by assignment.
And then I'm also storing weak references to it by using the WeakRef API.
And then to use the direct one, I'll just go a dot heavy and look at its first index.
And for WeakRef, I have to deref it first, which may or may not be undefined, which is why I'm using the optional chain over here.
Question mark dot.
Perfect.
And, and then I'm looking at, it's, first like same, thing I'm doing with a heavy zero.
I'm, this is essentially the same as that object.
Look at it's heavy member, which is this thing over here.
And look at the zeroth member of this array if it exists.
Let's run this within the browser.
So copy it over here.
Go into the browser command.
K.
So right now you can see that the strong reference, this thing over here, I'll just maximize this.
Is going to, be fine.
It's always going to be fine.
It's never going to give you undefined the proper program will crash before it lets you clear that memory.
But, in this particular case is no memory pressure yet.
So it's the WeakRef is also going to be fine.
You can see that I can access those those rows I filled the heavy array with, anybody know how to clear up memory, asks the garbage collector to run within Chrome.
So within the memory tab, there's this thing over here.
This you cannot see it or there perhaps collect garbage is what it says.
It used to be a trash icon, but now it's a brush.
Anyways, I've clicked that now, so it's, I've asked the garbage collected to run.
So now if I go to the console and run this code again.
Do you think I'm going to get undefined on these strong references?
So the A objects over here, which I'm completing directly, is it possible for me to get undefined here?
Cool.
That's obvious.
Do you think I'm going to get undefined on this WeakRef over here that I created?
So, slight heads of yeah, maybe, who knows that's the right answer, but because I pressed the garbage lecture and I've run this code so many times that I know that it would, it has been garbage collected.
So I'm getting undefined over here.
So that's, WeakRef.
Any questions about that?
You can use this to build your own weak data structures, but JavaScript does come with a few.
One of them is WeakMap.
So raise your hand if you, familiar with the map data structure within JavaScript?
Excellent.
So the way the map structure works is you can set any object to a value, and then it internally stores a reference to both of those.
Of course, using that object again, I can get the value so set.
Since I'm setting X to alpha, when I give it that X object, I get back that string alpha.
But even if I get rid of this X and Y, by assigning them to null, I can still get access to those original things that I set by using map dot key so I can get access to those X and Y objects.
You can see over here the div and the input, and that's why, these data structures are required for React to function, modern React because it uses these things to attach data to objects like the DOM, like the divs and in-person whatnot for reconcile it and whatnot.
So that it can do, its resynching of what, the DOM looks like and what, okay, this DOM element, why did I create this?
I can just look in the map that I have.
Cool.
But if, it uses a map then that would mean that DOM element can never go out of scope.
'cause I can then React would always be able to get a reference to that DOM element.
'cause the keys are being stored within the map.
So that is why we have these data structures called WeakMap and WeakSet, and they allow you to not get a reference to the original thing.
So I cannot from a WeakMap.
So all I've done over here is replace the map with a WeakMap.
SimilarPPII can set an object.
I can get an object, but if X and Y are gone, like I assign them to null, there is no way I can get access to them from the WeakMap.
WeakMap doesn't let you get its keys.
It only gives you value for keys that you might have.
So internally it uses WeakRef.
So that's a weird thing to say in that it's not because it's baked into the system.
If you were to write a WeakMap, you would use WeakRef to build it, but it's, this is more level, it's baked into the runtime.
Actually WeakMap and WeakSet came before Weakref, just as an aside.
So that's why.
So it doesn't store a reference to those memory objects.
So X and Y, if they are gone.
No way to access them, which means anything that you attach to them also gone.
So the reconcile, it doesn't have to worry about memory management as much.
It's Hey, that DOM element got deleted.
I don't care.
I can't get access to it.
Anyways, any data I associated with it can go away as well.
So here's a quick demo of, map, running out of memory.
This is gonna be a fun exercise.
I have five minutes.
Are there any questions?
Wanna be positive?
Go ahead.
Excellent question.
So in, in this, previous thing that I mentioned, I'm like, I'm Weakmap is working on top of the keys.
So if the keys go outer scope, of course they are going to go, it's not going to keep a reference to the keys.
Would the values stay still within the WeakMap?
That's the question right.
Let me give a bit more.
So like a number is not something that you can have as a key within a WeakMap.
Cool.
So weak key can only be a symbol or an object.
So things that can be garbage collected.
So you cannot use a number as a key within a WeakMap.
You can use it within a map, but not within a WeakMap.
So the demo is not particularly important.
You know that if, summary, if I use a WeakMap over here.
That memory is going to go away.
'cause whatever memory I'm allocating, I'm only allocating within that function.
If I use a map.
As long as the map exists, that memory is going to stay.
Yeah.
- WeakMap
- WeakSet
- WeakRef
- const
- let
- process.memoryUsage
- gc
- block scope
- null
- undefined
- closure
- WeakRef API
- optional chaining (?.)
- map data structure
- WeakMap
- WeakSet