 already take to what happened to the one-take-one this I know I know we were doing quite well at home won't we yeah this is a second take we're back well yeah we are in our new basement studio temporary accommodation but better than none I'd say yeah absolutely yeah and I thought you know I'm gonna throw us into the deep end oh there's no easing in with you coming back from the working from home filming from home era even straight okay straight in to debugging memory leaks everyone's favorite I I think it's the hardest thing to do with DevTools 100% degree yeah it just in general trying to do something like trying to figure out where where memory is going it's so much harder than you know figuring out CSS problems so much harder than figuring out performance problems it's it's already hard I think in garbage collected languages in general to figure out when is something being garbage collected and reason about that and now put on top that our we have tools and DevTools but they just give you numbers and then go you figure it out and we're gonna look at some of those numbers I figured out what some of them mean not all of them but some of them so what happened is someone on GitHub let us know that Squoosh I know this app yeah the thing we built that we couple more but yeah you know this is the one we still maintain yeah but they were saying it's using a lot of memory mean that's images it does images and they're big so that's that was the first thing I said it's like yeah I mean it's gonna and like no no no it's using more and more memory oh that's not good classic memory leak territory yeah so the first thing I want to do is like you know can I recreate this problem so I went into DevTools and into the old memory tab how did they figure it out they saw it in Task Manager they saw the memory going up and up and up which you can use as well but then it sort of mixes in like other processes it's harder to see I couldn't imagine it's the kind of thing where Chrome sometimes grabs memory and even though the process that the renderer process help freeze it back up Chrome doesn't give it back because it might need it or something yep that's classically the Chrome behavior I might need all of your memory so I'll take it now but yeah this is how I recreated it and we got a comment a couple of episodes ago where someone was saying look I watch your episodes on my mobile and I struggled to read some of the code can you make the code bigger sorry mate you're not gonna like this one because the tools are dense they're really really dense I think they are mostly glorified spreadsheets lots of numbers and so you either get big numbers but only see a third of the data available are you making smaller yeah I've zoomed it in as much as I can look I'll zoom it in a little bit more that's as much as we can do but I you'll see in these videos I'm struggling even with some of the column sizes because I've zoomed it maybe slightly too much anyway let's let's see how easy this is to understand the first thing I did is take a heap snapshot when you press that button what it does is it does an aggressive piece of garbage collection and then goes right finds everything that JavaScript has caused or so it's basically make sure that everything that can be deleted is deleted and then it measures how much you have on your heap because that's the part where JavaScript objects live well you'll see that there's a few icons there's a bin icon and there's a no icon and the bin icon what does that do that runs garbage collection it does yes well done whereas the no icon deletes things like you know the actual snapshot yeah traditionally the bin for throwing stuff away that's not what's happening here it's just for garbage collection but you don't actually need to press that it's done automatically okay and now I know an array has a shallow size of 590 000 which is 22 percent and it's very insightful yes and so it's like we're using two megabytes or 2.7 megabytes not very interesting but now I've opened an image I've taken another heap snapshot we're up to 80 megabytes that makes sense for that image I'd say like it's a do you remember the resolution like 3000 by something three it's a big image three by two so it's like six million pixels so 24 million bytes yeah I thought oh are you gonna see this maths through you're gonna do yeah sounds about right sounds about right excellent cool and that's yeah it sounded about right to me like just memory going up isn't a problem we've loaded a big image and now we have a big image is worth of memory being used we actually see this on the side there what I can do is do this comparison and say hey show me the difference between snapshot one and snapshot two and we can see here there's these three array buffers 40 megabytes each well there's two of them are 40 megabytes and one of them is significantly less how how did you know to look there because there's also many other things you could have unfolded it was the biggest things there which is usually the first thing to do is there anything like it's memory's gone up a lot is there anything big this stuff gets a lot harder if it's lots of little things thankfully for this one as memory debugging goes it was pretty easy because it's three big things so there's two js arrays that are exactly the same size I'm assuming that is the copy of the image on the main thread and the other one from the worker because we're probably going to have two copies of the image flying around see you raise an interesting point here is that I think if if I'm looking at performance I can just go to a random website and I can look at the performance timeline and I can come up with some decent recommendation I would say when you're debugging memory stuff you really need some knowledge of the app yeah which is what you are talking about there so I can actually tell you so what we've done here is I've clicked into one of these arrays and gone into the retainers which shows you all of the you know the array is the the leaf node and now we're going back up things that have references to that array and who's holding a reference to the thing that's holding the reference so we can see the next thing on the tree there is uh you and I clamped array and then the next thing is image data so there you go that's there's a big clue and yes we've got these two 40 megabyte things it's decoded image data it's the one on the left which is the original image and it's the one on the right which is the encoded version then decoded and the third one is 860k which is funnily enough exactly the size of the compressed image that's probably that one good eyes yes that is the encoded jpeg so that's the three and you know with app knowledge we can look at that and go it's fine working as intended working as intended so the next phase is to close that don't do a reload because we're looking for leaks yes so just close it within the app reload it and then do another heap snapshot and at that point see it's oh twice the memory twice the memory and again some app knowledge comes into this because that might be fine because it might be doing some caching you might be you might have a way to go back to the previous image you've got to keep it in memory yeah maybe um but we know we don't do that we do have some caching but we know the caching is gone when you press that yeah that x so you saw the summary right so i guess you could do a comparison that is what i'm going to do um so yeah we can see we've got like a lot more of those array buffers than we had before we had uh three before we now have six so it's just a comparison to snapshot one or snapshot two so this is just the raw snapshot three right now oh but let's let's take a look let's have a you know compare it to snapshot oh you can select which one to compare to yes and and so we can see now that there's some stuff being created some stuff being freed but we're still ending up with more than we intended this is not really a useful view because we we know that the there are these six array buffers still in memory but we really want to know well some of those we need because they're on screen like the the images are on screen what we want to know is like where are the ones that probably shouldn't be around anymore and yeah the fact that we have it looks like the ones at the bottom are the ones that are being freed so two 40 megabyte things were freed good because of well probably the previous image we didn't want those anymore and yet four 40 megabytes one are being created that seems like too too many yes absolutely and so what i've done here is i'm now showing the objects allocated between snapshot one and snapshot two that are still around in snapshot three okay yeah so these are the ones that we would expect to be gone because they were created you know for that first showing of the image but they are still there for snapshot three i didn't know this tool could do that and yeah sometimes you just want to use the the other kind of comparison but this this really narrows it down to the ones that we should be gone because if we know again with that knowledge like everything created between snapshot one and snapshot two should not exist once we go back to the landing page and start over that's a big red flag absolutely yeah so now it's trying to find out where you know why are they there yeah where do they come from yes and so again we can click into it look at these retainers which shows all of the things that are holding on to that in memory we're seeing its image data we saw that before right compressed like we know that that's a term from within squoosh's source code so that yeah that all makes sense and there is one red flag there in that list for me and that is detached html element so a detached html element is an html element that's no longer in the document and that isn't necessarily a bad thing you could have like a menu in your app yeah that you you put in the DOM and the user clicks the menu thing and then when they click away you take the DOM element out but you hold on to it for later yeah but again a bit of app knowledge comes into play here i know we don't it shouldn't so this makes me think we probably hold a reference to the storm element and one of our components states and don't hey you don't have to guess we've got tooling now ooh so what i would do is try and find out which element this is and a handy way of doing that is similar to what you've seen in other parts of dev tools you can right click and go store's global variable and there it is and it's our two up custom elements it's a good element so that's the one that's got it shows the image either side and you swipe either side and so it's out right and again that confirms to me we don't cache those no they really shouldn't be around from a previous run yeah so something happened here that caught me out when i was debugging this for reels is at this point it is useless to do another heap snapshot and do more testing because i have just stored something as a global variable and that will mess with the results same with console logs like if you're doing console logs it's going to it's going to create memory leaks because those things have got to stay in the console and be unfoldable yeah exactly so all right let's try and find out where this where this two up is why is it there so again you want to sort of dig further and further down or like you know towards the root of the tree there are these internal nodes those are c++ functions within chrome right apparently we might get those named at some point but they're all called internal node right now which is very very unhelpful but before that there is v8 event listener and we know what event listens are i'm assuming yeah and just above that is a link that we can press to some code and that takes us here on key down on key down sounds like an event listener is this all a ruse to tell me that a pr that i merged and reviewed introduced a memory leak yep but it's okay because there's another one that i i merged that caused the same problem and we'll look at that in a second but yeah so we can see that here we've got a window add event listener key down and that is the source of our leak the problem here is is that this listener is on the window object and that's going to continue to fire even after the element is gone because it's on the window so you know we create this two up element we take it away and but when you press keys it's still firing that event and we do the trick i think where this on key down gets bound to this so we don't have to worry about retaining this it's it doesn't but it has a closure to it it's an arrow function it's an arrow function okay same same deal then okay so the way of fixing this and apologies this is because this is a real world case it's a real world bug squoosh has written typescript apologies if you're not used to typescript but it's it's java script it's mostly java script this is our constructor for our custom element for the two up i'm going to go and find out where that that that event listener is it's further down here in the constructor for the for the event for the for the element uh this is with custom elements this is a bad place to be putting an event listener because what we want to do is we only need this listener when the element is in the document we should do it on it on attached callback attached callback connected callback connected callback close it's been a while i just realized since i've written a custom element shame on me no it's i had to look this stuff up as well uh so i think we already have a function for that there we go connected callback so it's just moving uh the listener there but then we need to do the reverse disconnected don't we we disconnected callback which is when it's taken out of the document so there it goes and uh if you copy and paste a lot like i do make sure you change it to remove event listener i have created bugs where my disconnected thing is actually just adding another listener do that and that is the problems well solved uh the right thing to do is to confirm that by going back into your app hit refresh pick up the new code and then just uh do the same thing open the image heap uh close the image open the image heap easy as that just remember those steps but it's still twice as much memory yeah still twice as much because it's not fixed is it uh it is fixed like when i first saw that i was like well either that wasn't a problem or there are more problems i can tell you now there were more problems great so we're back here again the same business interesting because now now the the retainer chain that's just sounds like something the the dentist puts in your mouth but yeah the retainer chain should be different now shouldn't it it is going to be different so we're going to look at the same array buffer and there is no detached html element there so we have solved the problem that's small win with zero effect uh but again we're now just doing the same thing i have a quick question then actually so this the chain of retainers is obviously a chain here but multiple elements can be holding a reference to the same leaf can we see all of them yes you scroll down you'll see them all okay but sometimes some of them are false uh because well because some of them will have a reference to it but it would have otherwise been collected so sometimes you need to find the one that is actually the real retainer it normally does a good job of making sure it's the one at the top not always okay um but again it's just looking at it and sometimes it's just finding out like where is this where is this stuff so clicking through some code this is a pre-ac component this time not a web component looking at the constructor isn't helping me at all figure out it knows roughly where it is but it's not helping me we've got again like this line wasn't very significant but it told me what component it was in but yeah it was insignificant it was just telling me it's this is an instance of this class okay um oh okay further down we've got an event listener there's a bigger clue um move around for this tooltip to get out the way and again here we've got on mobile width change we've got an event listener here um again it's it it's just a function so it's finding out well where is that made into an event listener and we've got width query add listener um which when i saw this i i then knew what it was because i'm familiar with this code but uh you know you can chase it this is not an add event this is add listener so this is some custom code no this is match media and match media uses uh you can use add event listener um but it it also uses add listener and safari doesn't support add event listener or it didn't at some point one point on stage in Chrome Dev Summit the last on-stage Chrome Dev Summit i broke the app before going on stage and it was because i replaced one of these things from add listener to add event listener and it didn't work in safari everyone in the audience was like it's not working on my phone that was why so i just use add listener now even though you'll see it draws a line over yeah because it's deprecated that's why that's happening uh with a pre-ec component that's actually fine there in the constructor but this is the same problem as before we've got a you know a width query this is something that's happening on the window object but we're using it with a component so it's keeping it alive so i just need to do what i did before this is not a custom element this is a pre-ec component so it's a different but like simple callbacks exist this one it's component will unmount remove listener job done deprecated deprecated it works it's fine and so yeah this is it just uh keep snapshot close open again mirror signal maneuver hey look at that and it started and that was it once i saw that it's like oh okay there we go actually eventlessness and yes both very similar problem and event listeners are going to be the problem the it is the cliche for me because it's always dangling eventlessness yeah unintentional globals or event listeners it tends to be so yeah that was it that was that was problem solved we shipped it excellent um you but you know what we've we've got uh i would say i've got a bias towards chrome you probably do as well because it's where the money comes from that pays the bills um but i thought i'd have a look at the similar tools in in other browsers uh so this is firefox uh and it has a very similar menu uh it has a heap snapshot button there we go 80 megabytes you get this oh that's nice yeah it helps you really tell where the memory is spent very quickly this is big so we make it big yeah there's an array buffer there's there's there's a bit of memory there's a similar list view i would say i i find these tools harder to use it could be biased but i find it really hard to find the attribution of what's holding on to two projects in this um especially when it comes to listeners and maps but here's a here's a fun one um so close it open again gonna take another heap snapshot there oh no 160 megabytes but this is the fixed version this is the fixed version i thought you're going to show me walk me through the same oh the whole thing no no no they're already making signals that this episode's getting a bit long so oh there's more leaks in firefox or are they they do they not run some forced garbage collection they don't you have to go to about memory and press this hidden button minimize the memory usage so basically it had twice the memory because the old references were still around nobody was holding on to them they are collectible but haven't been collected yet exactly and so i do that and we're back down to to 80 so there's a gotcha there's something to watch out for if you're using this that makes sense and then finally safari in their timeline look nice symbols not there at first i had to go into this this little edit menu and enable it but there's a memory line there but you hit record and you start doing stuff and it will show you look there's some memory gone up the same business again close it and open it again and oh memory's going up again so where's the forced garbage collection button oh there isn't one because this is a memory leak yeah so you get these little s things and they're heap snapshots you don't you don't get to choose when they happen but they always one always happens at the end which is useful so we can dive into that here and we see something that's very similar to what we had in chrome again i find attribution a lot harder to deal with in this one versus chrome but in this case we've got a lot of canvas elements oh and it says there's a reader article something um you can click through to it and it comes you on the console uh so reader article find the js elements with uh cash finding recs number 14 this is not our code no this seems like this is the reading mode i talked to a safari engineer and they went uh this is not your problem this is our problem we got to go fix this now so so even though yeah i'm not used to these particular tools it helped me find an issue in safari um so they're going to fix that now which is good yes and i i feel it is part of their reader code because this this bug doesn't well they were able to do a raw webkit build and confirm you don't have a memory leak in webkit you do in safari because of internal safari things uh yeah and it wasn't even the only browser bug i found as part of this i actually found that resize observers leak in all browsers um always good although it's now fixed in chrome and it's fixed in uh webkit and i think it's fixed in firefox as well i'll put a link to that in the description if you want to see more about that um but yes right now it's advisable to manually disconnect your intersection of servers and resize observers these slides took a long time to do because i kept running into actual browser bugs to solve but there we go um that is how to to fix some real in the wild memory leaks well yeah i learned something jake so did i so what i'm going to do here is i'm going to take a heap snapshot mm-hmm a heap snapshot the profile right let's see if i can go back and do that again