 Okay, okay. Oh, I guess I'm using this mic. Hello, hello, hello, good morning. Good morning, happy Friday. First I wanna say thank you to everyone. Thank you to the organizers. Thank you, all of you for being here today, even though I know it is the morning of the second day, which makes it a lot tougher to get here. So thank you for being here. It is a great honor to be here. I think this is my fourth year here, I think, and I love coming every time. So thank you. Today I'm gonna talk about taking out the trash. This is the title of my talk. First I'm gonna introduce myself a little bit. My name is Aaron Patterson. I'm also known as Tender Love. If you don't recognize me, this is what I look like on the internet. So my avatar looks a little bit different than me, but that really is me. So I work for a company called Github. You may have heard of them. I'm not sure. I've been working for the company for about three months now, and it is the first legit company I've ever worked for. So like I said, I've been working for the company for about three months, and it was really weird. I had to go down to, I live in Seattle. I had to go to San Francisco for a week to do training or whatever. And it turns out that at the company, everybody refers to each other by their Github nicknames. And like in person. And this has never happened to me before. All these people were calling me Tender Love, and I just thought it was really awkward. So, I mean, if you want to call me Tender Love, you can, but please call me Aaron. That's fine. That's fine too. I'm happy with my real name. So I should have called this, I should have called this Cat's Puns and Friday Hugs, because it is Friday and I love puns and cats. So I'm gonna show you, I'm gonna show you some of my cats. This is one of my cats. This is Choo Choo. Her tongue is too long for her mouth. So it always sticks out. This is a more famous one. This is Gorbachev Puff Puff Thunder Horse. My wife likes him better than the other cat. This is, he's trying to hide in this photo here. He thinks he is hiding. He is not. This is Choo Choo again. I really like her. She likes to sit on my desk while I'm programming and she always makes this face, which is like the same face that I make when I'm programming. I'm actually like. Sorry. Anyway, so I also love cats and code, just like our MC. So I wanna go over a few things that I've learned so far at the conference. So first off, I learned about some new Ruby features. We were taught, like Matt was talking about typing in Ruby and we learned about gradual typing and I want to, so I wanna demonstrate it here today. I have a live demo for all of you. So I'm gonna demonstrate gradual typing, okay? You ready? Yes, I did it on stage. And now soft typing, soft typing. Wait, this is a mechanical keyboard so if you want to talk about mechanical keyboards, you should come talk to me later. Okay, also I learned that we don't care. What? Dynamic typing. Dynamic typing, yes. Dynamic typing, this is very dynamic. I was also gonna do static typing where I don't move my hand, the keyboard. Anyway, so we don't care about small things which is why in Ruby 2.5 we're gonna remove the down case method. So no more small things. Also I thought it was, I was watching the presentation. I thought it was really cool that we're gonna have up case and down case and they're gonna support UTF-8 characters so we have up case and down case. But I was thinking about this and I'm like, well we've got up and down but we don't have widen and narrow end. That doesn't, we're missing those so I put together an implementation of that so you can call widen and narrow end. So look forward to this in Ruby 2.5. If you wanna get this functionality today go ahead to my GitHub repository. You can get this jam and have those. I also learned that there are no durians allowed at this conference but that is too bad because I love durians so I will eat them anyway. And... Sorry, I'm trying to work in a durian pun somehow. I appreciate all of you for enduring my puns. We also learned about teaching people to fish. I'm going to teach you all how to fish. Go ahead and look in your email today. All of you, you have an email from me. Just enter your bank account information and I will help you out. So also I learned from today, at this conference I learned from Terrence that learning a new language can keep me from getting rusty so that was an important thing I learned from his talk today. All right, so let's move on to the real topic of conversation. I'm gonna talk about GC today. I'm gonna talk about GC specifically. I'm gonna talk about Godfrey Chan. So I asked him, you know, what is your favorite memory and he said that it has been collected. His favorite food is pizza and I asked him on the bus this morning what is your favorite color and he said that it is ruby. Actually I said it was orange but I fixed that for him. Okay, no, no, no, no. I'm actually gonna talk about the garbage collector today and this is a very exciting topic and I'm so happy for all of you to be here at 9.30 in the morning to listen to talk about a garbage collector. So, you know, get ready to have a nap this morning. It's okay, you can sleep in. So we're gonna talk about, I'm gonna talk about the GC and MRI. I'm gonna talk about GC algorithms but I'm gonna talk about them specifically with regard to the algorithms that we use in MRIs so I'm gonna talk about some GC algorithms but I'm gonna focus heavily on the algorithms we use in MRIs GC but I think I'm also gonna talk about two sides of the garbage collector. I think it's interesting when you see people give talks about the garbage collector they typically talk about collecting memory but they don't really talk so much about allocating memory so I'm actually gonna cover both sides of that. The GC is responsible not only for collecting memory but also for allocating it so we're gonna cover that. So we'll look at collection algorithms, how Ruby actually reclaims memory and then we're also gonna look at allocation algorithms, how we actually create objects in Ruby and I'm also gonna touch a little bit, if we have time I'm gonna touch a little bit on the introspection API like the GC introspection API and MRI so you can actually get statistics about objects in your system. So I'm gonna talk first about the collection algorithms in MRIs GC so let's describe a little bit what type of collector MRI has. The type of collector, we can describe it as a mark and sweep collector, it's generational and it is also incremental so we can use these three adjectives to describe the garbage collector. So we're gonna cover each of those a little bit more in depth but first I wanna step back a little bit and talk about what exactly is a GC so we know that it's a thing that frees up memory for us but at a high level what does that really mean? If you think about the objects that are in your system so if you look at the objects when you're creating objects in Ruby you'll notice that they form sort of a tree thing here so for example if you look at this Ruby code on the left you'll notice that it kind of forms a tree where we start at the root and the root references an array which is that A variable and then that array references a hash and the hash references a key and a value pair so we can think of it as kind of a tree data structure. So when you're creating these objects in Ruby you're creating kind of a tree data structure and let's say we change this code a little bit and we assigned a nil into A and we actually cut that line up there with the root now the root is no longer holding a reference to that array and hash, et cetera so these objects are going to get collected. So essentially what our garbage collector is doing is saying okay we're gonna find everything in this tree that isn't referenced from the root anymore and we're going to free that memory up. So we can think of all of our objects as a tree and we can think of the GC as something as trying to find things that are not available in that tree anymore. So a few terms that we can learn from this are we have what's called a root set which is that very top root thing we have garbage objects those are ones that are no longer reachable from the root and then we have live data which is all the objects that you can actually reach from the root in that tree. So our GC's entire job, the entire job of the GC is to find those unlinked nodes and then free those up. So when we talk about GC algorithms all we're really doing is talking about how do we find those unlinked nodes, right? How do we get those? What is an algorithm for finding those and releasing those nodes? So the first thing we're gonna talk about is mark and sweep. This is a very simple GC algorithm and it has two distinct phases, mark and sweep which is why it's called mark and sweep garbage collector. So if we look at this let's pretend we have an object graph that looks like this. What happens is we go through a mark phase and the mark phase goes through, walks every one of these arrows and actually marks that object as something that we care about. So it starts at the root and follows each of those arrows through the tree. So you'll see that it'll follow through A and it'll follow through all these arrows until it marks all the objects. And then we go through a sweep phase and what happens in the sweep phase is any objects that were not marked we actually just free up those objects, they go away and we're left with the actual live objects. And at the end of the sweep phase we go back through all those objects and unmark them all. So now we're back where we started and we can continue on with the program. So right now the mark flag has been cleared. So the mark and sweep algorithm is very easy. It's a simple algorithm but we have some problems with it. It's a little bit too slow. We have to actually walk every one of those arrows every single time, look at every object, mark whether or not it's alive and then sweep through the whole thing. And another problem is that we have to actually stop the world when we do this collection. You may have noticed in older versions of Ruby if you're, especially if you're running a long process you'll notice like it'll just stop for a second and then do, you're like, oh it's just pausing. Maybe my iTunes is playing too much or something. No, it's not your iTunes, it's fine. It's actually the garbage collector just pausing, doing the mark and sweep phase and then continuing on. So the other problem is that we have to visit objects every single time. We have to visit every one of the objects in the heap and mark all of those every single time and this is not very fun. So we have to walk every object every time. So one way we can get around this is if we introduce what's called a generational garbage collector and the idea behind the generational garbage collector, the theory behind it is that objects in your system will die young. So typically objects, you're gonna allocate them and then they'll just go away very quickly. So the idea is that if we divide objects up into old and new objects, so if we have old objects and new objects, then maybe let's only look at the young objects, only look at the new objects. So the way this generational collector works is say we have a object graph that looks something like this. We do the same mark and sweep phase where we say, okay, well, we're gonna mark here. We mark B and D and then we sweep A and C. So those go away, but once that happens, then we take B and D and we move them into the new generation. So they go into the first generation, right? Now let's say the program continues on and we allocate some new objects. Those new objects are gonna get allocated in the young generation or generation zero. Then we go through the same mark and sweep phase. So we mark F and G, we also mark D. Then we know that E goes away and then F and G get moved into generation one. Oh, go transition, there you go. And then we do the unmark step again or we clear that mark bit. Now the interesting thing that you may or may have not may or may not have noticed is that when we're doing that mark phase, when we're walking those arrows, we actually didn't have to touch the B node. We didn't go to B because we knew that that one was old. So we're not dealing with B. So the nice thing about this is that we don't actually walk those old objects every single time. So we can speed up that mark and sweep phase because we're only looking at old objects once in a while. We only consider new objects frequently. But there is one slight problem with this algorithm and that's that let's say we have a graph that's like this, B and D are in the old generation and for some reason, something happens such that D actually allocates a new object E, right? That new E object is in the young generation and when it's time to go through our mark and sweep phase, we don't actually walk that arrow up there because we know B is old, right? We know B is old so we don't go through all those arrows and what happens is at the end of the mark phase we say, well, E hasn't been marked so we're just gonna GC that and now we've got this bad connection here. So your program will crash, we've freed up an object that actually is being used, it's actually live. So the way that we fix that is we introduce something that's called a right barrier, a right barrier and a remembered set and what a right barrier is is when we write that connection between D and E, when that arrow gets written, we keep track of that. We keep track of that connection inside of what's called a remembered set. So when that arrow gets written, we remember that. We put it inside a special set so that we know when we do our mark and sweep phase, we say, oh, well, we're gonna go look at that remembered set too. We need to mark E and then E goes into the old generation and we're free of our, we don't have this bug anymore. So we have a couple new terms from this. We have a right barrier, which is just keeping track of those interesting, what we call interesting arrows, which would be old generation to new generation arrows and we have a remembered set, which is that list of those interesting arrows. So generational garbage collectors, they're faster. It's faster because we don't have to walk as many nodes as frequently, but it's not quite as easy. We have to implement this right barrier. So it introduces some right barrier and a remembered set. So it introduces some complexity to our garbage collector. Now, unfortunately, we still have to stop the world with this algorithm. Even though we're walking fewer nodes, we still have to stop, go through that cycle and then continue on. So to get around this problem, we've introduced what's called an incremental garbage collector and an incremental garbage collector is what it says. It's something that you can do incrementally and we'll look at how that works. The way this algorithm works is that it uses an algorithm called tricolor marking where you have three colors. Wow, these names are so imaginative, I love it. All right, so we have three colors, white, black and gray. Now, those objects are objects that we're going to collect. Black objects, they have no references to white objects but they're reachable from the root and then gray objects are reachable from the root but they haven't been scanned yet. So we'll look at how all these colors work together. So the way this algorithm works is we pick an object from the gray set and then we move it to black. We change that over to the black set. Now, for each object that that one references, we move those into the gray set as well and then we just repeat step one and two until the gray set is empty. And then we know that everything that's left over that's white, those can be garbage collected. So an example of tricolor marking, let's say we have an object graph that looks something like this. We start out with A and F in the gray set because they're referenced from the root. We color those black, then all of their references go to gray, then we color those black. And now we know that we're done, we don't have any more objects inside of the gray set so we can take all the white objects and GC those, we can free those up so those go away. Now, the advantage of this algorithm is that we can interrupt any one of those steps so we can take a break at any time. So we do the gray to black coloring and we can just stop right there, we can continue on with the program. So what this means is we can perform each of these steps incrementally which is why it's called an incremental garbage collector. So what this means is that our halting time is actually reduced. What we do is we say, okay, we'll run the program for a bit, now we're gonna do one step of this incremental GC and then we're gonna continue on with the program. So our program halts for less time. But there's also a problem with tricolor marking and that's very similar to the generational problem which is let's say we have these gray colors here. Now, let's say we halted right now, we're gonna pause the program and we do the first step, we pause the program, we let the user code continue so the GC pauses and then your code continues and somehow F, this F object creates a new object. It allocates something new. So it's pointing at a G object now. We have this new object. Now, let's say the GC starts up again and it says, okay, well now we're gonna take all the gray objects and we're gonna color those black. So it colors all the gray objects black and it says, okay, we don't have any more gray objects so it's time to GC. So we collect that G object and now we have a bad reference again. That G object should have been live but it is no longer live. So in order to fix this problem, we actually introduce another right barrier. So we say, okay, when we write from F to G we're actually gonna save that off. So we have another right barrier and a remembered set. So we do exactly the same trick we were doing earlier. We just keep track of that and then we know that we can color that one gray later. So interesting things from this. The glossary to learn from this is we have incremental GC which means that we can halt the GC at particular times. We have a right barrier again which keeps track of rights to objects and then we have a remembered set which is those interesting connections. Now, all these algorithms, the idea behind all these is that we want to actually minimize tracing. So we want to minimize the number of arrows that we traverse when we reduce those as much as possible and we also want to decrease halting. So those are our biggest things. We want to reduce the number of arrows we walk because that's less work we have to do and we want to decrease halting because if the program halts, it means it can't be doing other things like servicing requests from a user or something. Now, things that our GC is not which I want to talk a little bit about, our GC is not parallel. It means we're not able to run this mark and sweep algorithm in another thread. We can't do that. We can't do it in parallel. It's also not real time which means we can't run MRI on things like that require real time feedback. For example, like, I don't know, maybe you're building a robot or something. You might not want to run MRI on that. You might want to use something like M-Ruby or something that has a more real time garbage collector. The other thing is that it's not compacting. We don't actually take these objects and move them around and I'm going to talk about that a little bit later. Those objects, when we allocate them, they stay in one particular place in memory. So we'll look at that when we talk about allocations. All right, so we've covered the way that we do object freeing in MRI. Now, I kind of want to look at allocation algorithms and the thing I want to look at first is I want to look at our heap layout. So MRI, we have a heap. Our heap contains all of our objects. Now, when a Ruby object is allocated, we don't actually call malloc every single time. We actually allocate that into a page and the reason that we do that is that malloc isn't free. Come on! Ah, you're killing me. I know it's early, but please, I'm so proud of this one. All right, so, oh, thank you, thank you. All right, so malloc isn't free when you call malloc at cost time. So what we try to do is we try to allocate one large chunk of memory. We allocate a large chunk or we call it a page or a slab. I prefer to call it a slab though I think it's referred to in the code as a page and the reason I like to call it a slab I'll get into a little bit later. Now, we allocate one large chunk of contiguous memory. This page memory is contiguous. So in each one of these, chunks of memory contains a linked list and yes, I should advance the slide. So each page holds a linked list. Now, this linked list, each member of the linked list is what we call a slot. So nodes in this linked list are called slots and though each slot is actually a Ruby object. So to make this a little bit more clear when we do an allocation, we'll allocate a page and it is just a big chunk of memory like this. And when the garbage collector allocates an object it'll just put one of the objects inside of the page. So each time we allocate an object we get a new object one after another. Oops, yes, there we go. So we have a linked list that's stored inside of this page. This list we call the free list. So all we have to do to allocate a new object is we find the next slot that's open in the free list and then hand that back to you as your Ruby object. So if we actually fill up a page, let's say this page is filled with objects. When we go to the free list we see, oh, it's filled. So let's just allocate a new page and now we have a bunch of other places to store objects. Now, each one of these pages, actually one thing I wanna talk about is when we're looking at the top of this linked list where we actually pull that object out when it's time to allocate an object we call that our Eden, so that's where objects are born. So when an object gets garbage collected it's actually pulled out, it's pulled out from this list so we free it from that, we free it from that list and that actually leaves a hole in the page which I wanna talk about a little bit later. But let's say we actually free up all of these objects, all of these objects get GC'd. If that happens then we'll actually free up this page so the page will go away so we can free that. Oh, I wanna talk about, this is exciting, I love this part. So I wanna talk about some interesting allocation hacks. Now, not every object requires allocation. So Matt's talked a little bit about this yesterday with integers, now not every object and you may or may not know this but not every object in Ruby actually requires us to pull something off of that page. So I'm gonna try and walk through how that actually works here. So one page, one page in Ruby is about 16K. This is just a size, it's a fixed number, it's a 16K page size. Now one object in Ruby is 40 bytes, okay? So we got a 16K page and we got 40 byte objects. Now pages are actually aligned there and what this means is that we say instead of malloc-ing the pages, we actually do what's called an aligned malloc and what this means is we ask the computer, okay, give me a chunk of memory but I want the beginning address of that chunk of memory to be divisible by some number, okay? So the address of that chunk of memory is gonna be divisible by some number and we choose 40. So we choose 40 as our multiple. Now 40 happens to be the size of a Ruby object. Now what's cool is if you start at 40 and you print out the binary representation of that number, so we're gonna say, all right, 40 times one, 40 times two, 40 times three, et cetera, as if we're walking through the size of those objects that we're adding, we keep adding an object to that list. If you look at the binary values of those, now if I'm gonna just show you this here, these are the binary representations of those numbers. If you look at the last three digits, you'll notice that those last three digits are always zero, okay? They're always zero. So what this means is we can actually use those last three bits to add some meaning to the number. So we'll say like, let's use those last three bits to indicate that that number is something special. So what we can do is we can use that bit to represent integers without doing any allocations. So for example, let's say we have the number two and we wanna represent that as a Ruby, we wanna represent that in Ruby. What we would do is say, well, let's have a flag of one, for example, and if we convert two to binary, we know that that is one zero. And if we shift that over, then we get 100 and we add the int flag to that and now we have 101. And if you look at that number, because those last three digits are not zero, we know that that was not allocated in Ruby's GC. If an object is allocated in Ruby's garbage collector, it's always gonna end in three zeros. And since this one doesn't end in three zeros, we know that it's something special. So what we can do is just take that number two, encode it like this and treat it as a Ruby object. And then to decode, we simply just shift that over, shift that over one. So using this information, we can actually figure out what is the largest fix num that we can allocate without actually pulling anything from the garbage collector. So for example, if we do two to the 64 minus one, you'll see that's what the binary representation looks like at 64, 64 ones. But if we subtract one from that, we get a big num class. And that's because we had to shift that over one. We lose one bit from that shifting. So we can't represent 63 bits. And if we do 63, we still get a big num. And the reason we get a big num is because the very first bit represents plus or minus, right? So if we actually do two to the 64, then we get a fix num. So we can see that that's actually the largest fix num that we can represent. Now, even if you add one to that, you'll still get back a big num. Now unfortunately, this is before Ruby 2.4. If you go look at Ruby 2.4, it's much more confusing. You can't tell the difference. They're all just integers, unfortunately. But if you look at the object IDs, you'll notice they're very, very interesting. Those top two ones there, those top two ones have very big object IDs. And it's because we actually use the memory address, the address of the object as the ID. So you can tell those two objects at the top, those two are not allocated in Ruby's GC and the bottom two ones are. And this technique of using those three bits, we call those tag pointers. So it's a pointer, it's some number stored in memory, but that number has special bits in it. So I love this hack. I think it's awesome. It's really clever, in my opinion. Now, if you look at the other objects in Ruby, many of these objects are tag pointers. Fix nums are tag pointers. Of course, we don't have fix nums in 2.4. Floats are tag pointers, true, false, nil, symbols. So a lot of these things don't actually require allocation from the garbage collector. So I wanna move on a little bit to talk about some allocation problems. And these are problems that we've been seeing in our application. Now, one problem that the garbage collector has is we have an issue with poor reclamation. So let's say we have three pages that look like this. They're filled with objects, right? Now, let's say the garbage collector runs and we reclaim some of these objects so they go away, right? We've reclaimed some of those objects. Now, unfortunately, I mean, what would be cool is if we could take these objects and move them around like this, then we could actually take this page and free it. It would go away. Now, unfortunately, we can't do that. That does not happen in MRI. We cannot move these objects. So what ends up happening is we have these three pages that are allocated with holes in them and we can't reclaim that memory. So unfortunately, this causes some problems for us where we have heaps that are too large. They could be smaller, but they're not. The other issue is that we have copy on write. We've experienced copy on write problems and where this comes from is I'm gonna explain how these copy on write issues occur. Now, unfortunately, a Ruby memory page is not an OS memory page, which is why I like to call those pages in MRI slabs. So an OS page is something totally different. It's a page of memory, but it's not the same size as a Ruby page. So one Ruby page is about 16K. On OS 10, an OS page is about 4K. I think on the Linux systems we have in production too, there are one OS page is about 4K. So one Ruby page contains four OS pages. Now, let's say we have a parent process and a child process. Parent process forked to have a child process and they're both pointing at something that looks like this. So they're both pointing at a Ruby page that looks like this. Now, let's say the child process allocates an object, sticks it in there. Now, what happens is when we write to that bit of memory, the operating system has to copy it. So it's copy on write. We wrote to the memory, it has to copy it. Now, unfortunately, the operating system doesn't copy just that little section. It actually copies one OS page. So rather than copying those 40 bytes, we actually copy 4K. So we wrote 40 bytes, but we actually got 4K copy. So what would be cool is, and this is something I'm trying to work on and test on our application, is if we could group old objects together such that we didn't have as many holes in our pages. So for example, let's say we had two types of pages. One type is probably old page and another type is just a regular page. Now, what we could do is, if we allocate objects, at the time we allocate them, if we know, hey, I think you're gonna be old, we stick that into the old page, then maybe we can group all those together so we don't get holes in them like that. So we'll allocate an old object, we know it's old, when we allocate objects that are unknown, then we just stick them into the normal pages. So you might be thinking, Aaron, how do you know what object is going to be old? It seems hard, it is not that hard. If you think about classes and modules in Ruby, all these things are objects and they're probably not going to get garbage-elected. When you do a class foo, it's probably going to become an old object, same with modules or constants or possibly frozen strings. So we know we can have some heuristics to determine like, hey, when we allocate this object, let's put it into the old page. The other thing I was thinking about doing and I'm not, I'm just testing this now is if we could statistically determine which objects are gonna be old and which ones aren't. So let's say we have some foo object and 90% of the time we allocate a foo object, it becomes old, then the next time we allocate a foo object, let's just stick that into the old page. Though, unfortunately, most objects, becoming an old object is a rare thing so I'm not sure if this technique will actually work for us but that's why I'm just trying to implement it and see what happens in production. So this will help us efficiently use space so if we can group all these old objects together then maybe we can reduce those empty slots without having to move objects. We could also reduce GC time with this technique because if we know that those objects are gonna be old and we mark them old already, then we don't have to go through those first two cycles of garbage collection in order to mark them as old. All right, so let's take a look at a little bit of some GC introspection tools that we have available to us. We use a lot of these tools to debug object leaks and just various GC issues in production. So MRI has a lot of GC introspection tools so for example, we can look at GC info. Now, the way to look at GC info is just with the GC.stat method and you don't need to read all these. Just go run that method. But one of the main reasons I went through talking about all these GC algorithms and pointing out the particular glossary in those particular terms is because each of those keys in this hash corresponds to that particular thing with the garbage collector. So if you know how the GC works, when you read all these keys, it's very easy to understand exactly what the GC is doing. So now that we've talked about all these techniques, go run this code, take a look at all the keys and you should be able to determine what each of those keys means. We can also get GC performance. So sometimes we'll look at the performance of our garbage collector and that's really easy with just a GC profiler. It looks, the code looks just like this. You can just enable the profiler and check reports for it. You can see how long each GC took. So just call this method and then call the report method to take a look at the performance of the garbage collector. We also use a lot of heap introspection. So we'll look at what objects are actually allocated. And my favorite tool for using this is object space dump all. And what this does is it takes your entire heap and actually dumps that out as a JSON file. And if you have a lot of objects, this can take some time. So be careful when you use this tool, but this is one of my favorites to use. You can actually dump one, this dumps out your entire heap, but you can actually dump one object at a time if you want to. You can just use object space dump to actually dump out one object. You'll see right there, that's just the JSON representation of that. And what's neat is if you use this dump method, you can actually see what happens to the object as you GC. So let's say we do a GC.start three times. You'll see that that object down at the very bottom, after the three garbage collections, it becomes old and uncollectible and marked. So you can see how these objects change over time, which I think is very cool. Also, not all objects in Ruby have a right barrier, and you'll see when you dump out a particular object, you can see which objects do and don't have a right barrier. So in Ruby, objects have three generations, as we saw from this. The last one is the third. We don't consider an object to be old until it makes that third generation. So another thing I really like to use when debugging allocation issues is object space. Go check out all the methods on that class, especially trace object allocations. I like to use this one, I like to use this one a lot. If you call this, if you look at this method, you can actually turn on tracing for objects. So that every time an object gets allocated, it remembers where it was allocated, what file, what line. So if you're having troubles determining where some object came from, enable this flag, and then you can ask it, hey, where were you allocated? Hopefully I am out of time. I don't know yet, I think so. Am I, am I? Okay, thank you, thank you so much. I, oh, please use this hashtag. If you enjoyed the presentation. Also I have stickers of my cat, so come say hi to me. I know I'm not supposed to have stickers, apparently that's not cool anymore, but I do. So come say hello, find me later, yes. Thank you. Hello. Thank you. Any questions from the audience? I know it's early, but you can do it. Yeah, I was trying to think of some questions, but my memory is maxed, and I want to GC, but take too much time. Hello. You're using a right power, right? Yes, yes. Is there a reason we can't just mock, so if a black object allocates an object and retains reference to it, is there a reason we can't just mock that allocated object as great during allocation? I don't think so. I mean, we probably could, though I don't, I mean, yeah, I don't see why not. There's, I don't think there's any significant overhead. We could try it. Seems like a good idea, yes. I've got a question? Yes. So you mentioned that classes and modules are the objects that probably will become old and will not be garbage collected. What if I use a lot of anonymous classes? Does it make it harder for GC to do its job and they should avoid it? No, I don't think so. If you use anonymous classes, that's fine. We can tell, so when we want to divide those, when we want to divide those up, we can tell like, okay, this class was allocated via class foo. We know it was actually allocated in the source where if you're doing an anonymous class, you're doing like class dot blah. You're doing like class dot new or something or module dot new. So we can tell the difference between those two, those two types of allocations. So I mean, feel, continue to do that. Feel free to do it. Any more questions? I think you guys need to keep seeing. Okay. I did it. Yay. Yeah, don't forget to take your cat stickers. If not, I'll take them all.