3

An interviewer asked me a weird question of which I couldn't find answer.

Is it possible to force an object to be garbage collected in Gen 1 or Gen 2 and not in Gen 0?

Nikhil Agrawal
  • 47,018
  • 22
  • 121
  • 208

3 Answers3

6

Yes.

public class WillAlwaysBeCollectedInGen1or2
{
  ~WillAlwaysBeCollectedInGen1or2()
  {
  }
}

Because it has a finaliser, and no code that ever calls GC.SuppressFinalize(this), then it will always end up in the finalisation queue instead of being collected when it first becomes eligible for collection, which means it will always be collected in a generation other than the first.

Of course, since we generally want the opposite to happen (objects are collected as soon as possible) this shows why one should not define a finaliser unless it's actually needed, and should always call GC.SuppressFinalize(this) if an object is in a state where finalisation doesn't do anything useful (most obviously if it's been disposed of explicitly, but there can be other cases).

So, the usefulness of this knowledge is this; in knowing what not to do.

By extension, you can force an arbitrary object to be collected in Gen1 or Gen2 only by holding a strong reference to it in such an object:

public class PreventGen0Collection
{
  private object _keptAlive;
  public PreventGen0Collection(object keptAlive)
  {
    _keptAlive = keptAlive;
  }
  ~PreventGen0Collection()
  {
  }
}

Because PreventGen0Collection is itself prevented from collection until at least Gen 1, as explained above, the object passed to its constructor is kept from even being eligible for collection on a GC sweep, and will hence be promoted to the next generation.

Again, this demonstrates code to avoid; being finalisable causes not just an object to be promoted, but a whole graph. Never use finalisers when you don't need to, and suppress finalisation when you can.

Jon Hanna
  • 110,372
  • 10
  • 146
  • 251
4

You can't... But you can extend the life of an object until some point. The simplest way inside a method is to have a:

var myObject = new MyObject();

... some code
... some other code

// The object won't be colleted until this point **at least**
GC.KeepAlive(myObject)

Or you can use GCHandle:

var myObject = new MyObject();
this.Handle = GCHandle.Alloc(new object(), GCHandleType.Normal);

(where this.Handle is a field of your object)

and perhaps in another method:

this.Handle.Free();

The description of GCHandleType.Normal is:

You can use this type to track an object and prevent its collection by the garbage collector. This enumeration member is useful when an unmanaged client holds the only reference, which is undetectable from the garbage collector, to a managed object.

Note that if you do this (the GCHandle "this") inside a class, you should implement the IDisposable pattern to free the this.Handle.

Ok... Technically you could:

var myObject = new MyObject();
GC.Collect(0); // collects all gen0 objects... Uncollected gen0 objects become gen1
GC.KeepAlive(myObject);

Now myObject is gen1... But this won't really solve your problem :-)

xanatos
  • 109,618
  • 12
  • 197
  • 280
  • 1
    Why won't the last piece of code solve his problem? His problem was the answer he was asked to give during an interview. Forcing a collection will promote objects from generation 0 to generation 1 thus fulfilling the request. That the problem is a silly one is orthogonal to this. – Lasse V. Karlsen May 18 '15 at 12:09
  • @LasseV.Karlsen I do consider a non-solution to move quickly an object to gen1 just to say "in this way it won't be collected while it is in gen0"... So the last response is formally correct, but morally isn't. – xanatos May 18 '15 at 12:17
4

According to the documentation of GC.Collect:

Forces an immediate garbage collection of all generations.

Then yes, you can do that. Simply force a collection.

As described here, Fundamentals of Garbage Collection, under Survival and promotions:

Objects that are not reclaimed in a garbage collection are known as survivors, and are promoted to the next generation.

As such, if the object was in generation 0 before you forced a collection, it will now be in generation 1.

OK, and then, what if I don't have a reference to the object any more? What happens if I force a collection then? Well, most likely it will be collected. Can it be fixed? Well, depends on what you mean by "fixed", but yes.

Add a finalizer to the object. A finalizer will make the object be moved on to a separate list during collection, ready for the finalizer thread to finalize it. Only after the finalizer has processed it will it again be eligible for collection but by then it has already been promoted.

Also note that if you add the finalizer because you need to handle the case of not having a reference you don't really need to force the collection any more since this is an interview question with a very specific problem, you have now guaranteed that it will survive generation 0.

So two steps, either of which will guarantee that the object is not collected in generation 0:

  1. Add a finalizer
  2. Hold on to a reference to the object and force a collection.

Caveat: Garbage collection is documented, but have been tuned various ways and times during the life of .NET. Whether the documentation and above text can be taken as documentation fixed in stone or whether there are optimizations done here that can for instance make GC.Collect not force a collection because a collection is already in progress I don't know.

Since you asked in the context of an interview I would say the above should be your answer to the interviewer, but don't rely on any of this in practice.


GUIDELINE: Relying on the internals of the garbage collectors is one way to make brittle code.

DON'T DO IT.

The only place I've seen good reasons for actually messing with GC.Collect, and even there it is dubious, is in a more or less single-threaded system that deals in big batches. For instance a Windows service that processes big batches of data periodically could do one batch, then force a collection to reduce memory footprint until the next batch comes due.

However, this is a highly specialized case. Other than satisfying an interviewer, GC.Collect should not (as a guideline) be present in your production code.

Lasse V. Karlsen
  • 380,855
  • 102
  • 628
  • 825