Subtitles section Play video Print subtitles [MUSIC PLAYING] JOHN MCCUTCHAN: Hey, everyone. I'm John McCutchan, and welcome to the Break Point. And I'm here with Loreena Lee. LOREENA LEE: Hi, I'm Loreena Lee. JOHN MCCUTCHAN: So what do you do at Google? LOREENA LEE: I actually work on Gmail, and I'm focused primarily on performance, so making it faster as well as making sure we're using the right amount of memory. JOHN MCCUTCHAN: Nice, well that's good. Good thing you're here, because we're here today to talk about understanding the way memory works inside JavaScript and how to profile your usage of memory inside Chrome dev tools. So I'm going to spend a little bit of time talking about the conceptual material for how JavaScript manages memory. And then Loreena is going to show us a really cool demo of using Chrome dev tools. So JavaScript variables can be of four primary types-- the boolean type, which just true or false canonical values, the number type, which is any double precision floating point number, the string, which is a UTF-16 encoded character array, or an object, which is a key value map. For those of you familiar with JavaScript, this is very basic stuff. But we're just going to start from scratch and build up. So objects are built around key value mapping. So the key goes in the square brackets. And this is always a string. No matter what you put in it, it's going to be coerced into a string. And then use that as the look up into the table. And then for value, you can store any JavaScript object inside of the value. So a key fundamental concept of memory and JavaScript is the object graph. So we're looking at a whole bunch of circles and lines on this slide. But let's start from the left at the blue circle. This is the root object inside the memory management system. This is not something that you can explicitly manipulate or do anything with, but this is just where every object that you create descends from. And so this is called the root. And from the root, it references global variables and other global state that's available to the browser. The red objects are object variables. So these can reference other variables. And the green circles are scalar variables, like a boolean or a number that can't reference anything else. And you can see the green note at the end there, on the upper right part of the screen with the yellow arrow, this is the terminating node in this part of the object graph. So objects have a concept of a retaining tree. And this is the paths along the graph that are keeping the object from being classified as memory. So on the slide right now, you can see a green node. And let's look at the retaining tree for this green node. So the retaining tree is this yellow node here-- the root node-- as well as this other path from the root to the other yellow node as well. So the object actually has two paths that are keeping it pinned into memory. If both of these paths are terminated, then the object becomes garbage and eventually is collected. Objects have two sizes. They have a shallow size, which is just the size of the actual object. And this is usually a very small constant value. I think on my system, an object is 36 bytes. All of the variables that the object references are accumulated together. The sizes of those objects are accumulated, and this is the retained size of an object. This is the amount of memory that could be freed if this object was turned into garbage. LOREENA LEE: If you go back to the previous slide, we can see the retained side of the root node would be basically the whole graph except for those two disconnected nodes in the middle. JOHN MCCUTCHAN: Yes, which are garbage. So from the root node, the retain sizes is the size of everything that's still reachable. So what exactly is garbage? Well, the definition of garbage is any variable and all variables which cannot be reached from the root node. So in this diagram here, I've highlighted the two nodes which are garbage. The red node is referencing in the green node, but there is no path from the blue root node to that red node, making both the red and the green node into garbage. So garbage collection happens in two phases. First, the graph is scanned, and all garbage is found. So we already know the garbage in this graph was the red and the green node. And then this memory is returned to the system during the collection phase. So it's important when optimizing for performance and memory usage to understand the cost model for allocating memory. So every time that your JavaScript application calls new, it reserves memory from something called the young memory pool. So memory is split into young objects and old objects. The v8 internally decides when to promote an object from the young memory pool to the old memory pool. You don't have any control over that, but when you call new, you're going to be scooping up memory from the young memory pool, which is very cheap. It's actually incredibly fast until the on memory pool runs out of memory. And at that point, the young memory pool is scanned, and all of the garbage is collected, freeing memory, making it possible to allocate new objects. And this could take milliseconds. So for interactive applications, you really want to be careful about how you allocate objects and what the patterns are, and how long you hold onto them, particularly for a game. You pretty much need to be able to do a frame with zero allocations, because inside of a frame, you have 16 milliseconds to compute the state of the world, render it, and take input handling, play sound. If half of that time is taken up by garbage collection, the game's not going to be very much fun. So in summary, all variables that you have in JavaScript are part of the object graph. And the only inner nodes in an object graph are JavaScript objects. They're the only type of variable that can reference other variables. And objects have two sizes-- the shallow size, which is just the size of this the object itself and then the retained size, which is the size of the object itself plus all of its descendants shallow sizes. So all variables that cannot be reached from the root are classified as garbage. And when v8 decides it's an appropriate time, it will do a garbage collection phase, freeing up memory and collecting all the garbage and giving it back to the system. So allocations are really cheap until the young memory pool runs out of memory. At that point, a garbage collection is forced, it's not just done at an opportune time. So you have to watch out for that in interactive applications. So Loreena, do you want to give us your demo? LOREENA LEE: Sure, so just a little bit of basics about the Chrome dev tools. So we'll talk about how you can use the dev tools to profile what's in your heap at any given time, and then also to find situations where you may be leaving around memory allocated that you don't need anymore. So in general, the garbage collector should be cleaning up after you. But there are certain situations, which we'll see in a little bit, where things may not get cleaned up as expected. So a little bit of his background on the dev tools. We can use these now. There are multiple tabs, and we're going to go through this in an example. But first, you want to if you have a suspicious action, so you see that your memory is growing, or you think that your application is slowing down for some reason or another, and you think that memory might be the situation, you can use the Timeline tab to perform the action and see what's happening to memory. So we can see if it's growing over time. And if so, then we can say, OK, now we need to dig a little bit deeper and see what's happening in the heap. So we can use that to capture a heap snapshot, which will show you all the objects that are in the graph that John just talked about. It does not capture scalar values, which were the green nodes-- green nodes? JOHN MCCUTCHAN: Yes. LOREENA LEE: Green nodes in the graph that we saw. But it does show you all the JavaScript objects that are allocated. There are four views of a snapshot. And you'll see these in the demo as well. There's the summary view, which shows you everything in the heap, and you can apply a filter on the entire graph to show only certain things. And I'll talk about that in a little bit in the demo as well. And then there's a comparison view, where you can view the differences between two heap snapshots. So you take one snapshot and then take another one and see what happens to memories. Did more things get allocated? Did a bunch of stuff get collected? What's going on there? There's a containment view, which is a bird's eye view of the app's overall object structure. So John mentioned a little bit about memory versus retained memory. And so in the containment view, you can see more easily what the retained memory is. And the DOMinator's also has a different view of the same summary. JOHN MCCUTCHAN: Yeah, so the summary and the comparison view are sorted and organized around the objects constructor, whereas the containment view is organized starting at the root. So it captures the structure of the object graph. LOREENA LEE: OK, so we're not going to cover the container and DOMinator's view too much in this demo. But there's some really great documentation online at developers.google.com, where you can see some nice demos that show you exactly what you can see by these two other views. So we talked about comparison view and how sometimes you can take two snapshots to find leaks. But we're going to talk about a case where two is not quite enough. Some other good tips to know, though, are, you should always do these experiments in an incognito window. You want to make sure that you're in a clean room environment. You don't have your extensions that may be interacting with your app in a way that you don't understand or you don't even know. So something these third party extensions do all sorts of crazy things to memory, and you want to make sure that those aren't influencing your results at all. JOHN MCCUTCHAN: Yeah, just to repeat that, any browser extension that is loaded will be part of the heap snapshot. So you're not just looking at your code. You're also looking at any extension that happens to be loaded at the same time. So an incognito windows is a quick way to avoid loading your extensions. LOREENA LEE: And then you want to get your bearings. So on the heap profile page, you'll see that when we show the heap, there's a lot of stuff in the heap. Basically, anything in parentheses you should just filter out of your brain for a little bit. You can just ignore them. A lot of the things are also going to be dimmed, and those are system allocations that we can't really control from your app. So those also should be ignored. And then the other thing to note is, when you click that heap snapshot button, a full garbage collection right before you click it before the heap is profiled. So you can assume that anything that v8 decided that it should GC should already be cleaned up. So let's move on to the demo. Let's see. Where are we? So we have this quick little demo. It really doesn't do a whole lot. There's one button. And when I click it, it will start filling up a cache. So I have a cache of five items that, whenever I click it, it fills in the cache of five. So the size is five. JOHN MCCUTCHAN: So what happens to the entries that are already in the cache? LOREENA LEE: When I first click it, there's nothing in the cache. So what we want to do is, in theory, when we flush the cache-- so by filling it with new stuff-- in theory, all the things that were in the cache before should be evicted. And in theory, we think that they should be collected. Let's find out what happens. So let's go ahead and refresh just so we know that we're loading everything from a clean state. Let's go back to the timeline. I mentioned this earlier. JOHN MCCUTCHAN: Yeah, that would be great, actually. LOREENA LEE: So we can go to the timeline. Let's say that we think that this is causing a memory leak. So we're going to go onto the memory here. And we're going to click Record, which is this gray circle at the bottom. JOHN MCCUTCHAN: It will be red. LOREENA LEE: And now it's red. So we're going to go ahead and say, let's do some work. And we'll just do it a few times, click it a few times. And then there's this little button here that looks like a trash can. And that will force a garbage collection. So let's just force it and say, OK, maybe it now can clean up whatever it was that I left behind. So hopefully, all those things that I added to the cache that had been flushed out, pushed out of the cache should be cleaned up. And we'll go and do some more work. So you can see that at the top, the memory is growing. So you see in blue there that there's some new memory being allocated. And when I click the garbage can, it seemed to have dropped a little bit but not quite to the baseline. So let's go ahead and stop this. And if I go and click anywhere on the top here, I can look at a specific part of the graph. And I can widen this window here to view whatever portion of the graph I want to see. So down below, you see this green line that kind of looks like a stair step. And each time I push the button, that is the number of DOM nodes that are allocated. So you can see that it goes up. JOHN MCCUTCHAN: So it looks like we're leaking DOM nodes. LOREENA LEE: Well, it looks like we're allocating DOM nodes. We aren't necessarily leaking them yet. So we don't know. The garbage collector may not have kicked in yet. And so they wouldn't get collected. JOHN MCCUTCHAN: Oh, that's right. We're not looking at the part of the graph after you clicked, garbage. LOREENA LEE: Exactly. Let's bring it out over a little bit wider so we can see what's going on. And right around where the dip is in the blue line is when I click the garbage button. And we don't see it going down. So that is the problem. And if we widen it even more, where I clicked a little bit more, you can see there's more stair steps up when I click the button. So we're pretty sure we have a leak. JOHN MCCUTCHAN: This looks pretty suspicious. LOREENA LEE: And on the left, there's the DOM node count. So it'll tell you what the range in DOM nodes allocated for the range that you're looking at. So from here, we started at 15. And now we've got 92. So that number never went down. We see that the green line pretty much never goes down. So now we can go back to the profile page. And let's refresh again, so we can flush the cache, start with a clean slate. And now we're going to take a heap snapshot. So take a heap snapshot, and click Start. So it's super fast, because this is a pretty lightweight thing. So this is our first snapshot. JOHN MCCUTCHAN: So I see a lot of things in parentheses right up at the top. LOREENA LEE: So we're going to go ahead and ignore those. These are compiled code, system arrays, things that we really can't control. So go ahead and ignore those for now. And we'll just scroll down a little bit. And you can see that we've got all sorts of stuff in our heap. JOHN MCCUTCHAN: So everything in this table is sorted by the constructor call. So it's not the variable name or the path that you could follow to get to the variable. It is the constructor. LOREENA LEE: So at the top, it says, class filter. So if you wanted to search for a certain thing-- I know that we have our caches of objects called stuff. So if I type in, stuff here-- JOHN MCCUTCHAN: Very descriptive. LOREENA LEE: Well, there's nothing here, because we haven't allocated anything yet. So that's expected. So let's clear this out. And let's do some more work-- or do some work, we haven't done any yet. And we're going to click and collect another snapshot. So if we click on this snapshot now, you can see that we've got a 1.3 megabyte heap, whereas before we had a 1.2. So now we get to those different views we talked about earlier. So right at the bottom here, it says, summary. And right now, we're looking at a summary view, and we're showing all objects in the view. And so that's why we see this long, long list of things. So let's go to Comparison View. Let's say we want to know, what's the difference between snapshot two and snapshot one when we click that button? And so it says we're in Comparison View, and we're comparing with snapshot one. And so these are the differences. And it's a much shorter list, because we filtered out now. JOHN MCCUTCHAN: We really culled a lot of it. LOREENA LEE: So these are now showing us only the differences between one and two. So you see we have this, stuff object. And I told you that the cache was a five item cache. And so there are five items. And way over here, you can see, if we highlight the stuff, the delta between snapshot two and snapshot one is plus five. So there's five new objects of stuff, which is exactly what we expected. JOHN MCCUTCHAN: So this column here is the number of objects that were created of that type, this is the number that were deleted, and then this is just the sum of the two. LOREENA LEE: Correct. So this is expected. We have a cache of size five. We push the button. We filled the cache. So this is what we were saying earlier is that OK, well, there's no leak here. We expected to see five. There's five. So now we need a third one. So let's try that again. Let's do some work and go ahead and collect a third snapshot. Memory is going up. We are at 1.4 now. And if we diff with the second one again, we'll see that there are-- oops, there we go-- JOHN MCCUTCHAN: Another five stuff for-- LOREENA LEE: And that's fine. We allocated five. But we didn't delete anything. So, hmm. So if we look back in the Summary View here, we'll see that there are now 10 objects of type stuff. JOHN MCCUTCHAN: And this matches what we saw in the timeline graph, where we were never seeing anything go down. It just kept going up on a fixed size, seeing the stair step in. LOREENA LEE: So another thing that's great to see now is that we can go into the Summary View again. And we can say, show me the objects that were allocated before snapshot one. And that will show you that. And there shouldn't be any stuff in this, because we hadn't filled any cache. But now you can say, show me the objects that were allocated between snapshots one and two. JOHN MCCUTCHAN: So we should see five stuff. LOREENA LEE: So we should see five things that were allocated between snapshots one and two. And those things we expect have been flushed out of the cache by the time that we did the third snapshot. And so if they're still hanging around in the heap in snapshot three, that's probably our problem here. JOHN MCCUTCHAN: One thing we should notice here is that when we're looking for the summary of objects allocated between snapshots one and two, you can tack on the sentence that are still alive at snapshot three. So from the moment in time that snapshot three was taken, these are objects that were allocated between one and two that are still alive. So we should not see any stuff here, if we weren't leaking memory. LOREENA LEE: Correct, exactly. So we are leaking memory. And we do see that there are five objects that were allocated between subjects one and two that are still alive in snapshot three. So if we go ahead and expand this, you can see here that the stuff, we can click on one of these. And on the bottom, you'll see the retaining path. And it'll tell you that it's being retained by the object data, which is in this HTML div element. And this helps you figure out where you're going to go and fix the problem. So you can tell exactly where this is retained from who's holding on to the handle from it. JOHN MCCUTCHAN: So remember the retaining path is the path from a garbage collector root to the variable that's being held. LOREENA LEE: So the other thing to note is, here, none of these stuffs have a background that is colored yellow. And if we look at the Summary View with everything that's in snapshot three, we can look at stuff. And we should see that half of them are yellow and half of them are not. JOHN MCCUTCHAN: So why are they yellow? LOREENA LEE: So the yellow background indicates that there's a JavaScript handle on this object. So there's a way to reach this object through the JavaScript. And if it's not colored yellow, there's very likely not to be a JavaScript handle, which means you're going to have a hard time cleaning it up. You've probably lost its reference to it. It's still on the DOM tree, but you lost your JavaScript reference to it. JOHN MCCUTCHAN: So you could regain a reference to this by walking the dog. LOREENA LEE: By walking the-- right, so it's not like it was in the previous slides where we showed you that they were true garbage that had zero path to the root from the root. So it does have a path from the root, but there's no way to access it from JavaScript. So this is why this is a JavaScript memory leak and not necessarily a garbage collectible leak. JOHN MCCUTCHAN: So the yellow indicates that there is a path. And white indicates that there is not. LOREENA LEE: And not a JavaScript path. And if it were true garbage like we had seen in the graphs that John presented earlier, it would be with a red background. And those are things that are in the [INAUDIBLE] DOM tree. They cannot be reached from the root of the DOM node. But they're being retained by something. So a lot of times in those cases-- and there's some good examples of this on the dev website that have them highlighted in red. And those are things that are being retained for some reason or another. but. They're not reachable from the root of the DOM tree. And so in those cases, you should be able to open the retaining path. And you should see something highlighted in yellow, which says you have a JavaScript reference to this thing. But it's not in the DOM tree at all. And that should help you. JOHN MCCUTCHAN: So these are detached DOM nodes. LOREENA LEE: Right. And there's a great example of that online as well so that we can go through that. So I think that's pretty much the demo. So we can recap it. Let's go back to the slides and recap. So we presented when Comparison View just isn't enough. And we showed that not all memory leaks result in detached DOM tree nodes. There are things like unintentional unbounded array growths, so you add things to an array to be processed later. And you forget that you need to clean it up at some point. So either you process it and you never dumped it. So these kinds of things can be caught with this three snapshot technique that I just presented. And there's one other case. That's lingering event handlers retaining DOM nodes that are otherwise detached. And that can be seen in the scenario that I just explained, where you would have something in red with a retaining path that has some yellow nodes in it. And so that's when you would want to bring out your three snapshot technique. JOHN MCCUTCHAN: So these are some closure function that is attached as an event list node to DOM node that is out of the DOM. LOREENA LEE: Correct. And this is just a graphical representation of what we just did. But it's much better to see that in the dev tools themselves. So hopefully this was helpful. JOHN MCCUTCHAN: Yeah, I think so. I learned a lot. LOREENA LEE: Sounds good. JOHN MCCUTCHAN: So I think we have a little bit of time. So here's a little quiz, the tale of the missing object key. So if we look at the source code on the slides here, you see a new object is constructed. It's empty. A key called, double was added and assigned to double value. A key called, integer was added and assigned an integer. Then when you look at the object, you can see the key double and integer are there. And the two values are there. So if you were to then take a heap snapshot and look at the heap snapshot and find the object, o, inside the containment view, you'll notice that the integer key is missing from the heap snapshot. That seems a little weird, because we just confirmed that it is inside o, and it is in fact inside o. But what's going on here is that v8 has some tricks in how it stores integers. If the number fits into a signed 31-bit integer, then it's actually stitched into the object graph structure and not actually part of the object graph, which is kind of interesting. So look out for that. There's also cases where a property is backed by a getter. And so when you do a heap snapshot, the heap snapshot can't call the getter because it might alter the program state or might be incredibly expensive to call the getters, because there could be many thousands of these objects with these getters. It avoids doing that. So you might see when you take a heap snapshot that some keys are missing. But they're still there. LOREENA LEE: Good things to be careful. JOHN MCCUTCHAN: So thanks a lot for joining us, Loreena. LOREENA LEE: No problem. Thank you. Thanks for having me. JOHN MCCUTCHAN: Bye. LOREENA LEE: Bye, everyone.
B1 snapshot memory garbage heap john object The Breakpoint Ep. 8: Memory Profiling with Chrome DevTools 95 17 Hhart Budha posted on 2014/06/16 More Share Save Report Video vocabulary