 Okay. Hello. I'm talking about to build your own GC in 20 minutes. Yeah, it's important to notice the punctuation here. Put your commas right. So this is not about I'm telling you to do this in 20 minutes. I only have 20 minutes to explain it to you. So I'm Roman. I've got a lot of help from Alexi. He wrote a nice demo. I will show you later. So let's start with the basic question. What is a garbage collector? When you think about garbage collection in Java VM, it's usually you think about finding all this garbage and reclaim it and, you know, manage all the stuff in Java. But what it really is, it's really an acronym for memory manager. It's more than just managing garbage. So this is the obvious thing that you think about. When you think about garbage collection, it's basically a replacement for the free call that you have in C and C++ programs. But it also manages allocations, right? The way you do garbage collection affects how you can allocate memory. Some garbage collectors might use a free-list-based allocation. Some others support like pointer bumps, allocations. Some others might actually require to take a lock or whatever. So this is important. You can actually also affect the layout of objects in memory or you can allocate from, I don't know, the cloud or somewhere else. And it also needs to manage access to objects because the garbage collector might require like card marks or some other things to move around objects or whatever. So the guiding principles for the GC interface, when we built it, was, first of all, that each GC should limit its own source directory and the GC owns the heap. I explained this in a couple of seconds. So when we started with the GC interface, up to JDK9, we had this mess. So we would have a GC somewhere and then all over the place and runtime interpreter and C1 and C2 compilers and some other places, management, JVMTI, we would all have some little bits of GC specific code. So our first goal in JDK10 with the JAP304 GC interface was to clean that up and isolated GCs from the rest of the JVM. So that we have like a little bit of shared GC code and then we have all the existing GC serial, parallel G1 and CMS, especially CMS because you probably noticed that it's deprecated. We wanted to isolate this because it was all over the place and hard to manage and all those things. So this is our first push to the GC interface in JDK10. In JDK11, the epsilon and ZGC have been added and we extended the GC interface to cover those, especially as ZGC required the load barriers that haven't been there before. It's important and a couple of little scaffolding for this. And with JDK12, we added support for Shenandoah, which kind of completed the GC interface to cover all access, including primitive heap access and object equality and whatever we needed for and allocations for supporting Shenandoah. So if you were to build your own GC, where do we start? The important entry points that you need to keep in mind, they are basically two of them. The most important ones is collected heap. It lives there in the hotspot source directory. It's the central interface for any GC implementation. It has a lot of support for basically managing everything else. It gives you the barrier sets. So this barrier set is important if your GC needs any kind of memory barrier for intercepting writes or read or whatever else. And yeah, this is where you start. Let me show you some code. Alex C managed to write a GC in not in 20 minutes, but like one hour, two hours. It was probably a good contender for world record in writing a GC. So let's see if we have it here. All right. So actually, did the epsilon GC, which is kind of a no-op GC, does nothing? So most of it is actually just reusing whatever is in the shared GC code, right? Presentation mode. Yes. Right, this IntelliJ. It's a three-line few presentation mode. Very useful. OK. Wow. Nice. So we have this is an epsilon heap. It's a subclass of collected heap. And we implemented a bunch of stuff like initialized function to set up the actual memory allocator stuff, et cetera, et cetera. So this is all just skiff holding. We have an epsilon barrier set. Can we jump in there? Let's see. Control-click. No, not really. Yes. You see it's basically empty. It's epsilon GC doesn't require any barrier. If your GC would require any barriers, you would add them there. You will probably notice when you do this that there's a lot of brackets there that make the code look like XML. It's not my fault. I apologize for this. Other than that, let's jump back. So there's a lot of little things here. Warnings, basically. For the purpose of this demonstration, Alexi added an actual GC implementation. A Lisp2-style mark-compact GC, which I'm trying to show you now. Let's see where it is. You see it's a very small GC. It's only 600-something lines of code. So when the system runs out of memory, it jumps into this entry-collect method. What this mark-compact does is it traverses the heap and identifies all the live objects by marking them. And then in the second phase, it will go over all live objects and compute the new address and compact the heap to the object to the start of the current pointer. And then in the final phase, it will move all the objects and update all the references to it. So let's see all the phases. First, we need to clear the bitmap. It's done here. Very simple. Second phase, do the marking. So we have a marking stack. We have the scan closure, which is the important piece here. Let's see what it does. Doesn't work reliable. Let's see. Yay. So this basically says, for each reference that we encounter, we load the object and we mark it in the bitmap. And then we push the reference. No, we push the object on our work stack for processing next. So let's see if we can go back. And then we start out with processing all the GC roots, which is the thread stacks and all those things. This will initialize our working work queue and with a bunch of objects. And then we just say, okay, let's see. As long as the work stack is not empty, we take the next object and do the same thing, traverse over each reference and mark through them. It's very simple. Next phase is to calculate the new locations of each object. So we have a new closure here. It's epsilon-calc-new-location-blah-blah-blah closure. Let's see what it does. This basically says, for each object that we have, we see each live object, first we decide if we need to preserve the mark words. Because we are going to overwrite the mark word with the new location. So the current compact point is tracked here. It starts out with the start of the heap. And then you just say, for each object we update this mark word with the current compact point and then we update the current compact point the next possible location after the object. That's all. And we do this for every live object in the heap by saying, work this bitmap that we just created in the previous phase. After we're done, the new end of the heap is just the current compact point, that's all. So next phase, we need to adjust all the pointers in the heap. So most objects have pointers to other objects to update all those references to point to the new locations. This is what this guy does. Let's see. This is the epsilon adjust pointers, blah, blah closure. It just says, for each object traverse all the references. So this is a trampoline to another closure. And this one says, for each reference, load the object, look up the new location and write back this new location to the reference and we're done. Right? And then we do this for all live objects by walking the bitmap. Then we need to do the same for the roots, the thread stacks and all the other things that are there. It's the same closure that we have seen before that updates the references and we process the roots of them. Finally, we need to process the preserved marks with the new locations. And then last step, actually move all the objects to the locations that we just computed. We have the epsilon move objects closure. And this is pretty, this does the much of the work. It just says for each object, for each live object, if we have a new location, just copy the whole object to this new location, using this. And then we reset the mark word because we overrode it with the new location. That's all. We work the live object with disclosure and that's it. And then we have an epilogue where we take care of a couple of things like updating all the C2 pointers and all these little things. That's it. That's a whole to see in 20 minutes. Not really. Okay, how do I get out of this mode? I also want to show you that this actually works. Extra presentation. Let's see. So Alex also wrote a little demo for this. It's... This fires up this print pet clinic. It seems to be a popular little demo. Takes a little bit to come up. Just wait for it. Right, there we are. I am looking for a little message that says how much heap that we have. Ah, here. Whoops. Here it says we now occupy, like, 20% of the heap. It's not really that much at this point. So here we are at one... More or less one gigabyte heap occupancy now. Yeah, okay. Let's press the button and see what happens. So we ramp up heap usage. You see, and at some point GCE will kick in and we have all those phases here. Ah, wait a second. So we have... It locks all the phases of the GCE, the prologue here, bitmap and so on. And we compact the heap from 3.5 gigabyte usage to just 35 megabytes. Um... The whole thing took 250 and something milliseconds. Um, and still the latency here is significantly larger than this. Um, if we run it the second time it should look better. Because the first time we had all sorts of ramp-ups going on. So, yes. And, yay. So the latency here, the maximum latency here is 260 milliseconds, which sort of corresponds with, not quite, but almost to the time that we actually took for the GCE. Um, yeah. That's it, I think. Ha, we can do it even more. Cool. Questions? Need a microphone? You can also just shout at me and I will repeat it. Sorry. Java bindings for the GCE interface. Um... Not yet, no. You want to write it GCE just using Java code? Yeah, it's going to be interesting. Yeah, there's GraVM, which is a compiler written in Java, but it doesn't allow you yet to write a GCE in Java, sorry. You always need to go back to C++ for this, currently. Yeah, Jyx did and still does, right? So, you could do this. Huh? Oh, really? Cool. Yeah. If I don't want to write GCE from scratch can I somehow invoke the default one from my custom GCE? You want to reuse an existing GCE and then somehow extend it? All right. So, I mean, this is one of the things that Epsilon GCE does. It reuses a lot of code from... a lot of existing code and that is basically shared between GCE. So, most of the stuff that you saw here did hide the actually the work of like walking all those objects and this thing. The point is that it's already reusing a lot of shared code. I don't know if it makes sense to reuse like G1 and then you probably could, yeah? Like have one generation managed by one GCE and one by the other but it didn't really prepare for this yet. So, yeah. But there's a lot of shared code in this GCE directory that is for you to use. So, if you collect a heap it should give you a good start. I have a general question. So, if I have a Java method that creates an object with new but the pointer never leaves the method so I could also go ahead and just create the object on the stack instead of the heap. Does this Java do this or is there any way to analyze like if the object really needs to go to the heap or if we can just... Well, this is what the compilers do. They do an analysis and if they find that the object never escapes the stack then it just inlines it. Okay, so that's out during compile time and not during... Yeah, if you see you wouldn't see that. Okay. Is the interface automatically include all the Java management interfaces? So, do you need to add extra bits for the system.gc or getting the VM sort of size or is it actually... already? So, if you go... If you call system.gc it will end up with calling one of the collected heap methods and for the management stuff there's an extra... I don't know what it's called, management support or some... Yeah, something like this. So, we have APIs for this and it's actually... I think this took most of the time for LXC2 to implement in this little demo. The actual gc was figuring out all this freaking other stuff, little details. But yeah, we have APIs for this. Anything else? No? Cool. Thanks.