Question

In my application there is a specific time when a number of large objects are all released at once. At that time I would like to do a garbage collection on specifically the large object heap (LOH).

I'm aware that you cannot do that, you must call GC.Collect(2) because the GC is only invoked on the LOH when it is doing a generation 2 collection. However, I've read in the documentation that calling GC.Collect(2) would still run a GC on generations 1 and 0.

Is it possible to force the GC to only collect gen 2, and not include gen 1 or gen 0?

If it is not possible, is there a reason for the GC to be designed that way?

Was it helpful?

Solution

It's not possible. The GC is designed so that a generation 2 collection always also collects generation 0 and 1.

Edit: Found you a source for this on a GC developer's blog:

Gen2 GC requires a full collection (Gen0, Gen1, Gen2 and LOH! Large objects are GC’ed at every Gen2 GC even when the GC was not triggered by lack of space in LOH. Note that there isn’t a GC that only collects large objects.) which takes much longer than younger generation collections.

Edit 2: From the same blog's Using GC Efficiently Part 1 and Part 2 apparently Gen0 and Gen1 collections are fast compared to a Gen2 collection, so that it seems reasonable to me that only doing Gen2 wouldn't be of much performance benefit. There might be a more fundamental reason, but I'm not sure. Maybe the answer is in some article on that blog.

OTHER TIPS

Since all new allocations (other than for large objects) always go in Gen0, the GC is designed to always collect from the specified generation and below. When you call GC.Collect(2), you are telling the GC to collect from Gen0, Gen1, and Gen2.

If you are certain you are dealing with a lot of large objects (objects that at allocation time are large enough to be placed on the LOH) the best option is to ensure that you set them to null (Nothing in VB) when you are done with them. LOH allocation attempts to be smart and reuse blocks. For example, if you allocated a 1MB object on the LOH and then disposed of it and set it to null, you would be left with a 1MB "hole". The next time you allocate anything on the LOH that is 1MB or smaller in size, it will fill in that hole (and keep filling it in until the next allocation is too large to fit in the remaining space, at which point it will allocate a new block.)

Keep in mind that generations in .NET are not physical things, but are logical separations to help increase GC performance. Since all new allocations go in Gen0, that is always the first generation to be collected. Each collection cycle that runs, anything in a lower generation that survives collection is "promoted" to the next highest generation (until in reaches Gen2).

In most cases, the GC doesn't need to go beyond collecting Gen0. The current implementation of the GC is able to collect Gen0 and Gen1 at the same time, but it can't collect Gen2 while Gen0 or Gen1 are being collected. (.NET 4.0 relaxes this constraint a great deal and for the most part, the GC is able to collect Gen2 while Gen0 or Gen1 are also being collected.)

To answer the question "why": physically, there is no such thing as Gen0 and Gen1 or Gen2. They all use the same memory block(s) on the virtual address space. Distinction between them really is made only virtually by moving around a imaginary border limit.

Every (small) object is allocated from the Gen0 heap area. If - after a collection - it survives, it is moved "downwards" to that area of the managed heap block, which eventually was just freed from garbage. This is done by compacting the heap. After the full collection finishes, the new "border" for Gen1 is set to the space right after those survived objects.

So if you would go out and try just to clear Gen0 and/or Gen1, you would open up holes in the heap which must get closed by compacting the "full" heap - even objects in Gen0. Obviously this would not make any sence, since most of those objects would be garbage anyway. There is no point in moving them around. And no point in creating and leaving large holes on the (otherwise compacting) heap.

Whenever the system performs a garbage-collection of a particular generation, it must examine every single object that might hold a reference to any object of that generation. In many cases, old objects will only hold references to other old objects; if the system is doing a Gen0 collection it can ignore any objects which only hold references to those of Gen1 and/or Gen2. Likewise if it's doing a Gen1 collection it can ignore any objects which only hold references to Gen2. Since examination and tagging of objects represents a large portion of the time required for garbage collection, being able to skip older objects entirely represents a considerable time savings.

Incidentally, if you're wondering how the system "knows" whether an object might hold references to newer objects, the system has special code to set a couple bits in each object's descriptor if the object is written. The first bit is reset at each garbage collection, and if it's still reset at the next garbage collection the system will know it can't contain any references to Gen0 objects (since any objects that existed when the object was last written and weren't cleared out by the previous collection will be Gen1 or Gen2). The second bit is reset at each Gen1 garbage collection and if it's still reset at the next Gen1 garbage collection, the system will know it can't contain any references to Gen0 or Gen1 objects (any objects to which it holds references are now Gen2). Note that the system doesn't know or care whether the information that was written to an object included a Gen0 or Gen1 reference. The trap required when writing to an untagged object is expensive, and would greatly impede performance if it had to be handled every time an object is written. To avoid this, objects are tagged whenever any write occurs, so that any additional writes before the next garbage-collection can proceed without interruption.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top