Subtitles section Play video
[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.