Tuesday, 11 January 2011

.Net Garbage Collection basics

Of course I'm not not by far an expert on Garbage Collectors, but I have a basic understanding of how .Net GC works. As with many other of my surface knowledge areas, it's subject to the same cycle of "learn - don't need it for a while, so forget - need it again, so relearn again". The relearn part tends to be painful, cause you're aware of being trying to find the same links and articles that you had already read some time ago... So summing up some of the great info around the net, adding the pertinent links and my own conclusions could save me a lot of time in the next occasion (and posting it will force me to complete it).

While writing it I've decided to split this GC entry in two separate posts. This one will deal with the GC internals, and next one with what all this means for us, developers.

Some theory first
.Net has a generational garbage collector (same has mono since version 2.8) . When a memory allocation takes place (the new operator, just before the constructor gets called) and there's not enough memory available, the GC routine runs. Depending on the kind of GC that you have selected (Workstation or Server) the GC code runs on a separate thread or on the same thread that tried to do the allocation. When running on a separate thread, we talk about concurrent garbage collection, which means that during good part of the GC the other threads can continue to run and there should not be a noticeable "freezing effect". This is an improvement over the traditional approach of "stop the world" while a GC is running. With .Net 4 Concurrent GC was improved and renamed to Background GC, Background garbage collection, making the chances of noticeable stops even lower. Basically, in the pre BG Collection times, if when the GC is going through the Gen 2 objects (that could take some good time) the other normal threads running in parallel and doing new allocations fill the segment where new objects are placed (the ephemeral one, will talk about it later), these threads would get stuck, cause they have no more space for their new objects. Now, with the background collection algorithm, all threads (including the one doing the collection) would get halted at this point and a new GC would be launched that would just take care of this ephemeral segment (this ephemeral segment collections are now called foreground collections), releasing space and allowing the threads (normal application threads and the background collection algorithm thread) to run on.

.Net heap consists of 2 heaps, the small object heap (SOH) and the large object heap (where objects larger than 85000 bytes are stored). Objects in the LOH belong to the Generation 2 (of course Gen 2 also contains SOH objects). Generation 2 objects are only collected when a full garbage collection takes place. Garbage Collections that only take care of Generation 0 and 1 objects are called ephemeral collections in the .Net jargon, should be very fast (no long paths to traverse to build the graph of reachable objects) and because of this won't be launched as Concurrent-Background collections. The segment (16 MBs in current implementation) where Gen 0 an Gen 1 objects are allocated is also called the ephemeral segment, and is always the last allocated segment.


Ephemeral generations must be allocated in the memory segment that is known as the ephemeral segment. Each new segment acquired by the garbage collector becomes the new ephemeral segment and contains the objects that survived a generation 0 garbage collection. The old ephemeral segment becomes the new generation 2 segment.

full text

While the space in the SOH is compacted after a GC is run, it's not the same with the LOH space, this means that non careful allocation of large objects can lead us to fragmentation problems. Compacting the SOH involves great performance gains when compared to memory allocations in traditional native application. Allocations in the SOH are done linearly, the runtime maintains a pointer to the next free position in the SOH (in the ephemeral segment) so the next allocation is directly done there, contrary to allocations in native applications, where it's necessary to find a free memory area. Also, this means that objects created one after the other get allocated contiguously which will also improve performance. Check the excellent Generational GC Performance Optimizations section here for further info.

Finalization
Garbage collection is more than just releasing memory, it also involves releasing unmanaged resources (windows handlers...). We should put the code for releasing these resources in a Finalize method, so that the runtime can release them even if we forget about it.
So, how do the memory release and resource release work together?
It's pretty well explained in the articles and questions-answers listed below. When the GC runs it assumes that all objects in the heap are garbage, so to find which objects should be reclassified as not garbage it begins walking the GC roots (stack variables and global variables mainly) to form a graph of reachable objects. Once done, all objects not in this graph are not accessible by the application and correctly classed as garbage. It's now, when before releasing that memory, the Finalization things comes into play. The GC checks if these unused objects are referenced from the Finalization queue (all objects overriding the Finalize method inherited from the Object class are added to this list while being created), those for which the answer is Yes are removed from the Finalization queue and added to a new list, the freachable queue (that is also a GC root, meaning that objects referenced by your finalizable object are also prevented from being collected at this point), and are no longer considered garbage. That means their memory is not reclaimed in this collection. Once the GC finishes, a special application thread, the Finalizer Thread, will be waken up by the fact of having new entries in the freachable queue and will start to run the Finalize method for the objects there. Once the Finalize for an object is run, the object is removed from the freachable queue, so next time the GC runs it won't be added to the graph, and the object memory will be released.

further reading:

No comments:

Post a Comment