The cost of finalizers
A common pattern we use is to wrap objects that require disposing of resources with a dispose / finalizer. Here is a simple example:
This is a pretty simple case, and obviously we try to avoid these allocations, so we try to reuse the SecretItem class.
However, when peeking into high memory utilization issue, we run into a really strange situation. We had a lot of buffers in memory, and most of them were held by items that were held in turn by the Finalization Queue.
I started to write up what I figured out about finalizer in .NET, but this post does a great job of explaining everything. The issue here is that we retain a reference to the byte array after the dispose. Because the object is finalizable, the GC isn’t going to be able to remove it on the spot. That means that it is reachable, and anything that it holds is also reachable.
Now, we are trying very hard to be nice to the GC and not allocate too much, which means that the finalizer thread will only wake up occasionally, but that means that the buffer will be held up in memory for a much longer duration.
The fix was the set the Buffer to null during the Dispose call, which means that the GC can pick up that it isn’t actually being used much faster.
Comments
A good practice is to split the finalizable object into different class that contains just the handle, so your finalizable object is the smaller possible and contains few references as possible.
Considering you really need to create your own finalized instead of deriving from a SafeHandle
Felipe Pessoto, I don't think there's a need for a critical finalizer here as long as all the resources are managed (but I might be wrong).
If you are calling Dispose, which calls GC.SuppressFinalize(this), it wouldn't prevent the object to move from finalizable queue to f-reachable queue?
@Pop Catalin, I was reading from mobile and didn't notice the Finalizer is using a managed object. So my first comment doesn't make sense.
Felipe , No, SuprressFinalizer does NOT remove the object from the finalizable queue.
Yeah, but wouldn't it prevent it to move from RegisteredForFinalization to ReadyToFinalize queue? So the lifetime should be the same as a object without a finalizer, preventing the object to be unnecessarily promoted as well.
I use these posts as reference: https://blogs.msdn.microsoft.com/cbrumme/2004/02/20/finalization/ https://blogs.msdn.microsoft.com/tess/2006/03/26/net-memory-leak-unblock-my-finalizer/, but I may have understood it wrong.
Feilpe, That would have been my assumption, but that isn't what is actually going on. Any finalizable object is there, and will remain there until checked by the finalizer thread, it seems. A lot more expensive that I would have assumed, under correct case of calling the GC.SupressFinalize
Not seeing much benefit in that finalizer in the first place.
If you're holding some secrets (passwords, keys) in that buffer, and trying to prevent them from appearing in memory dump, this is a NOTORIOUSLY poor job.
If your buffer survives collection, GC may relocate it as part of compaction/defragmentation phase. Sure, clean the buffer in finalizer — but the copy of your buffer still exists in some unallocated area in the heap. Or multiple areas - if the buffer survived multple compactions.
The conventional way here is to allocate sensitive buffer in unmanaged unmovable memory.
Oleg, I'm not actually using it for this. The demo here was to show off some scenario where a finalizable value holds a big buffer and the implications of that. Probably a poor choice of sample
Comment preview