.NET Internals Cookbook Part 9 — Finalizers, queues, card tables and other GC stuff

This is the ninth part of the .NET Internals Cookbook series. For your convenience you can find other parts in the table of contents in Part 0 – Table of contents

59. What happens if an exception is thrown in finalizer? What about infinite loop?

Let’s see this code:

If you run it in dotnetfiddle you get this:

So you don’t see anything printed out but you do see the exception.

If you try it in tio:

So you see all lines and exception thrown out of bands.

And if I try it locally with VS 2017:

So it looks like your mileage may vary. However, it kills the application, generally, as specified in documentation:

Since finalizers are run on a separate thread, you get deadlock with infinite loop:

60. What is finalization queue? What is f-reachable queue?

When your object has a destructor (finalizer), it is stored in finalization queue when allocated. Later, when the object is not reachable anymore and GC detects that, it puts it to f-reachable queue. Finalizers are not called directly, they are called on a separate thread which gets the objects to cleanup from f-reachable queue.

Let’s take this code:

Let’s start it and attach debugger on first line read:

So we can see that there is a Foo object in finalization queue. We can see its roots:

So there is one normal root (our variable) and a finalization queue.

Before we move on, let’s freeze the GC thread:

Let’s carry on and look for a trash (just hit enter twice and get to the third line read), break in debugger and check:

So our object is still there, let’s find its roots:

So it is not held by anything and ready to be cleaned up — it is now in f-reachable queue:

The address 0063a034 was in a finalization queue (see previous outputs). Since we froze the finalizer thread, the object is not cleaned up.

61. What is object pinning?

When you pass an object to some external (non-managed) code it is important to not move the object as the external code may have a pointer to it. Since GC can move the object when compacting the heap, we need to ask GC to not move it. This is called pinning.

To pin an object you can use either fixed keyword or GC.Alloc.

62. Does P/Invoke pins objects?

Yes, but only for the time of the method execution. You need to pin them manually if you pass them to some asynchronous code (which will use the object after P/Invoke call returns).

63. What is a write barrier?

This is a general term used in garbage collection, not something internal to .NET. Some GC algorithms need to be aware of any write you perform, for instance to update the card tables or reference counters. So when you perform assignment, you call write barrier doing some bookkeeping. See this code:

When you run it you should see that after the collection foo object is in generation 1. Let’s decompile it:

There is nothing special in line foo.Bar = new Bar();. Let’s see it with WinDBG:

You can see the write barrier here:

See the next question to understand how it can be used in .NET.

64. What is a card table? Brick table?

GC is capable of cleaning generation 0 only. What happens if the only reference to some object X (being in generation 0) is in a field of object Y which is in generation 1? How does GC know that it cannot remove X from the memory? This is thanks to the card table.

Object X must have been assigned to field in Y somehow. When this assignment was performed, .NET executed the write barrier (see previous question) which updated the card table.

Card table is a list of bits where each bit maintains 128 (in x86) or 256 (in x64) bytes of memory. When a bit is set, GC knows to include those bytes of memory as roots when performing the marking phase. Since card tables can be pretty big, they are grouped in card bundles (sometimes called card decks).

Brick table is similar structure covering 2kB and 4kB of memory (in x86 and x64 respectively). It is used by the GC to locate objects (technically plugs).

Bonus — where do I learn more?

Pro .NET Memory Management by Konrad Kokosa is a great book describing .NET GC into a great depth. If you want to learn more about memory reclamation internals then it is a must read.