-1

We have a BitmapImageclass that wraps a native image id and System.Drawing.Bitmap and implements IDisposable. The image id is created as part of a third-party imaging SDK we use that uses integers to identity an "image" pointing to some pixel data. Both the image id and bitmap point to the same underlying pixel data.

The SDK is very difficult to use correctly in many cases, so we have a facade layer that abstracts away the SDK and provides an easy to use and error-less API, for TIF and PDF documents, and part of this is to guarantee that memory is freed as soon as possible. 300 DPI multi-page images with hundreds of pages are common place, so memory can easily be high in the application.

We are currently calling GC.Collect in the Dispose method to free up the memory immediately. After thorough testing of the software, this was the only way to free up large amounts of memory immediately after releasing the underlying pixel data, especially during a large merge operation where we might be merging several hundreds of pages together in a document. It is also implemented this way so developers do not incorrectly try to scatter their code with GC.Collect, because they should not really be calling it anyway.

My question is two parts:

  • When a finalizer is called by the garbage collector, does it also free the memory immediately, or could there be a long period of time before this occurs? Should we call GC.Collect here as well? Especially in only a 32-bit process, we must ensure we are keeping as much free memory as possible.
  • The SDK we use is GdPicture, and even when you Dispose of their graphics object, it does not dispose of the pixel data or image references. It leaves them around until a developer frees them. We need to guaruntee that if a developer does not call Dispose manually, that the resources are freed. Is it appropriate to reference a managed class in a finalizer, such as GraphicsObject.ReleaseImage(id)? I read in some places that you should not call methods other than some of the static ones from things like SafeHandle, but unless we call this the memory will not be freed
David Anderson
  • 13,558
  • 5
  • 50
  • 76
  • The finalizer is not called by the garbage collector. It is called by a special finalizer thread. The garbage collector simply puts objects that need finalization into the finalization queue which the finalizer thread takes from. Note that the finalization queue is a GC root so the object's references are kept alive until it has been removed from the finalization queue. – Mike Zboray Feb 08 '18 at 21:09
  • That makes sense, so in a scenario where a developer did not call Dispose we are left with waiting for memory to be freed once the finalizer has left the queue? The best scenario would be to do a bug fix and ensure Dispose would be called, but if not.. is this correct? – David Anderson Feb 08 '18 at 21:14
  • Yes generally if Dispose is not called you have to wait for the finalizer to run and in that scenario the object requires 2 GC cycles for collection. The first one moves the object to the finalization queue. The second collection actually removes it from memory because now it is no longer rooted. You should make sure you are calling GC.SuppressFinalize in Dispose to prevent needing 2 GC cycles. – Mike Zboray Feb 08 '18 at 21:27
  • *Is it appropriate to reference a managed class in a finalizer, such as `GraphicsObject.ReleaseImage(id)`?* No, that’s generally a bad idea because there is no guarantee that `GraphicsObject` hasn’t been finalized already. Inside a finalizer you should only be releasing unmanaged resources you are dealing with *directly*; if it’s an `IDisposable` then simply trust the pattern is implemented correctly and if it isn’t, change your 3rd party library or file a bug. – InBetween Feb 08 '18 at 21:36
  • @InBetween - yes, but on the other hand a proper `Dispose()` should allow for multiple calls so disposing from a destructor ought to be harmless. The `if(disposing)` part is just belts-and-suspenders safety. – H H Feb 08 '18 at 22:02
  • @InBetween `GraphicsObject.ReleaseImage` is the _only_ way to release the underlying image because of the SDK, so if the developer does not call `Dispose` and we don't release this in the finalizer, during a merge operation there will in a matter of seconds be several GB of RAM utilization, so in this case what other option is there? Would using a `WeakReference` in this case be applicable? – David Anderson Feb 08 '18 at 22:06
  • @HenkHolterman is it guaranteed at that point that the disposable object hasn’t been finalized/collected (not disposed) already? I’m not absolutely sure. – InBetween Feb 08 '18 at 22:06
  • It could have been finalized/disposed but certainly not collected. – H H Feb 08 '18 at 22:09
  • `GraphicsObject` would eventually be collected and the finalizer would run and if the IDisposable pattern is correctly implemented the finalizer would call Dispose. But fighting a bad developer is a lost war, what’s the problem crashing if they don’t write correct code? – InBetween Feb 08 '18 at 22:10
  • When you wait for the GC to call the finalizers you have already lost the performance war. Consider throwing an exception from the ~finalizer. – H H Feb 08 '18 at 22:12
  • @DavidAnderson-DCOM "was the only way to ..." - but is that important? You haven't made the case for calling Collect() yet. – H H Feb 08 '18 at 22:21
  • At least in my testing, if we don't call GC.Collect after each page is copied and merged and no longer needed, the GC is doing work, but not necessarily after each page is no longer referenced/in scope, it might be every 1-3 pages it seems that GC kicks in, which in a concurrent scenario can mean a lot of memory usage. We probably could get away without it, but my question on using `GraphicsObject.ReleaseImage(id)` in the finalizer still sort of remains. GraphicsObject is a private field. Merging is a critical function of our business domain (banking), so we we're trying to keep it optimal – David Anderson Feb 08 '18 at 22:26
  • "a lot of memory usage" is not a problem by itself. I know it coluld cause them, but it seems you should be more alert on unmanaged memory (no GC) and the LOH (different GC rules). – H H Feb 08 '18 at 22:29
  • I think this has helped clear things up a bit. I can only look so far into the SDK because it is obfuscated, but it appears that we are dealing with unmanaged memory, with the only method of freeing it with that ReleaseImage method, so is this safe to call from the finalizer, or would be best just doing code reviews and bug fixes to ensure that Dispose is called? In our testing calling it from the finalizer does not seem to have any side effects, but that doesn't mean there won't be – David Anderson Feb 08 '18 at 23:03

2 Answers2

0

I think there's a bit mess. Want to make it more clear in common understanding.

You can't manage time for GCollection.

GC-n occurs when: System has low physical memory + Memory that is used by allocated objects on the managed heap surpasses an acceptable threshold. This threshold is continuously adjusted as the process runs. + GC.Collect method is called. In almost all cases, you don’t have to call this method, because GC runs continuously. GC.Collect method is primarily used for unique situations and testing.

GC optimizing engine determines best time to perform a collection, based upon allocations being made. Exact time when Finalizer executes is undefined. To ensure deterministic release of resources for instances of class, implement a Close() method or provide an IDisposable.Dispose implementation + The finalizers of two objects are not guaranteed to run in any specific order + Thread on which the finalizer runs is unspecified. For case someone forget to Dispose use Destructor

~ YourClass(){Dispose(false);}

is transformed into:

protected override void Finalize() { try { … cleanup…} finally { base.Finalize(); } }

  • Don't forget to

YourClass : IDisposable

It's ("Finalizer") used to perform cleanup operations on unmanaged resources held by current object before object is destroyed by GC. Method is protected and therefore is accessible only through this class or through a derived class. By default (when use ~), GC automatically calls an object's finalizer before reclaiming its memory.

GC adds an entry for each instance of the type to an internal structure finalization queue - an internal data structure (Queue) controlled by GC. Finalization queue contains entries for all objects in managed heap whose finalization code must run before GC can reclaim their memory. When GC finds objects to be garbage, it scans Finalization Queue looking for pointers to these objects. If a pointer is found, it is removed from Finalization Queue and added to Freachable Queue. Object is no longer considered garbage and its memory is not reclaimed. At this point, GC has finished identifying garbage. Special runtime thread empties the Freachable Queue, executing each object's Finalize method. Normally when Freachable Queue is empty, this thread sleeps.

You need minimum 2 GCollections for objects with destructor (That's why in Dispose pattern GC.SuppressFinilise(this) is used - you say to GC that don't move object to Generation_1 or Generation_2, that memory might be reclaimed as GC meet destructor) + (that also means that objects with finalizers are moved minimum to Generation_1, that GC checks about 10 times rarely (than Generaation_0) that means that objects you don't need stay in memory longer)

Suggest to apply or

using(...)

that transforms into

try { … } finally { XX.Dispose();}

or implement correctly IDisposable interface using IntPtr structure for unmanaged resources (it works with different architecture (32 or 64)). Do not recommend to use SafeHandle (Interopservices) because it works only with Win32 architecture (good in SafeHandle is that you don't need to implement IDisposable as it inherits from CriticalFinalizerObject)

Classic implementation of IDisposable when you don't need to care if someone forget to Dispose object (for that case you have ~ destructor):

For base class

public MyClass() { this.reader = new TextReader(); }

public class A : IDisposable   
{
    bool disposed = false;  // Flag: Has Dispose already been called?
    private Component handle;
    private IntPtr umResource;
    public void Dispose()   {      
                Dispose(true);   GC.SuppressFinalize(this);   }
    protected virtual void Dispose(bool disposing) {
                if (disposed) return;
                if (disposing) {            // Free managed resources
                        if (reader != null) reader.Dispose(); 
                        if (handle != null) handle.Close(); }
                if (umResource != IntPtr.Zero) { // Free unmanaged resources
                            umResource = IntPtr.Zero; }
                disposed = true;   }  
    ~A() { Dispose(false); }   } // Only if we have real unmanaged resources

For derived class

public class B : A  {
        private bool disposed = false;
        protected override void Dispose(bool disposing)  {
                    if (disposed) return;
                    if (disposing) {  ... }     // To free any managed objects 
                                               // Free unmanaged resources
                    disposed = true;     
                    base.Dispose(disposing)}   }

disposing false – call comes from Finalizer; disposing true – from Dispose (from you)

Sorry, forgot to RESUME:

** Use GC.Collect to force the system TO TRY to reclaim the maximum amount of available memory. You can also point out to GC which generations you want it to check. BUT AS I WROTE IN THE BEGINING - YOU HAVE NO CONTROL, even if you use GC.Collect - that doesn't guarantee anything (GC may override your call, etc), but you can try and it wouldn't bring any problems **

Gleb B
  • 151
  • 2
  • 8
-1

As a general rule, you should not have GC.Collect in productive code. It can help for debugging (if you want to see if a memory spike is temporary). But generally it is better to leave the GC to it's own devices. It is about as optimized as it can be. Even when it is not collecting, it is doing something effective (waiting if maybe application closure will make it's job a lot simpler). If you want to affect the major behavior, change teh GC strategy for the application instead.

The 2nd part of your question is about the Dipose/Finalize pattern. There are two cases:

  • You only handle stuff that implements IDisposeable. In this case all you need to do is Implement Dispose. And all it needs to do is to relay the "Dispose" order to anything Disposeable it contains.
  • You actually handle Unamaged resources. In this case you should first implement the Finalizer (so at least the GC can clean this up at applicatin closure/collection). Then you provide the Dispose function as additional thing. But I would always put the Finalizer first, because it will be called with guarantee.

Finalizer and Dispose are largely indentical. So identical it is customary to just write the Dispose function with a boolean switch. The only things in wich Finalizer and Dispose actually differ is the "relay" part: - If applicable you always relay a Dispose call to contained instances. Because that is the common usecase for Dispose (relaying teh call of Dispose()). It is better if we all adhere to it. - You never relay a fianlizer call to contained isntances. Finalization is between that instance and the GC.

About 95% of the cases you only need dispose.

Calling GC.Collect in the Finalizer makes no sense. The finalizer is about cleaning up unmanaged resources. The ones the GC does not govern.

GC Collect is just "do the collection of managed resources now". They apply pretty much to opposite cases (managed vs unamanged resources).

Christopher
  • 9,634
  • 2
  • 17
  • 31
  • 1
    _the Finalizer first, because it will be called with guarantee_ - no, it doesn't come with any guarantees. – H H Feb 08 '18 at 21:59
  • The full Dispose pattern is so verbose because it caters for derived classes with unmanaged resources. Make your class `sealed` when you omit the finalizer. – H H Feb 08 '18 at 22:00
  • @HenkHolterman: Please give the propability of a User forgetting to call Dispose vs the freaking Garbage Collector forgetting to do it's *ONE JOB* at application closure. Clean on closure is the one unambigious advantage the GC has. Everything else about it has Pro/Cons, but that is the biggest Pro it has. I know from experience. I learned with native C++ handling naked poiners. Honestly if not even the GC runs at closure, something went so wrong it is out of your hands anyway. – Christopher Feb 08 '18 at 22:04
  • [When everything you know is wrong, part one](https://ericlippert.com/2015/05/18/when-everything-you-know-is-wrong-part-one/). It's the fifth myth down or so. – H H Feb 08 '18 at 22:07
  • @HenkHolterman: if somebody calls SupressFinalize outside of dispose, all bets are off. There is some level off stupidity I simply can not imagine. This is smack into it. I mean with the Exception handling articles I read I can at least asume honest mistakes/lack of knowledge. But that would be Sabotage. And if the code goes into a infinite loop, a StackOverflow or OOM Exception will properly cancel it, with full GC run at the end. – Christopher Feb 08 '18 at 22:12
  • Doesn’t the OS reclaim all memory from a finished process anyways? Finalizers aren’t there to reclaim memory on process shutdown, the whole reason of the disposable pattern is to free unmanaged resources while the application runs... – InBetween Feb 08 '18 at 22:21
  • @HenkHolterman: Basic behavior of GC? I knew that. Myth 1? Wait, there are seriously people that think that??? Myth 2: I explicitly wrote that down above. Myth 3: Did not knew that. But I would Dispose of instance via using anyway. Myth 4: Sabotage. I have no defense. Nothing I can do.Myth 5: Infinite loops end with Fatal Exceptions. Finalizer runs. Myth 6: That is Myth 1, other side. Myth 7: Again, not Disposing like that. Myth 8: FUBAR. No defense. Ignore. I stopped there, because it became jsut repetitive :) – Christopher Feb 08 '18 at 22:22
  • Yes I know that, but if an app is terminated, what exactly is holding unmanaged resources? The way I understand it, the OS cleans everything up at that point. Finalizers are there to free stuff while your app runs in case you haven’t called Dispose and the object is collected. You can’t have a memory leak of an app that isn’t running anymore.. – InBetween Feb 08 '18 at 22:25
  • @InBetween: The OS will NOT reclaim memory. Every process has to do so explicitly. In the age of naked pointers, foretting to release something you allocated was one of the classical sources of memory leaks (https://en.wikipedia.org/wiki/Memory_leak). And it the one thing that became a non-issue thanks to the GC. As for unamanged resources: Nobody is "holding" them. You aquire them. You have to release them. Or they will stay in use until reboot. That is what Finalize and the final GC run are for. – Christopher Feb 08 '18 at 22:27
  • The OS will _always_ reclaim _all_ memory and _all_ resources of a terminated process. You really have to claim resources in another process (eg a buggy SQL server) to create a leak that way. – H H Feb 08 '18 at 22:31
  • That’s, in general, false. The link precisely states that memory leaks happen in a *running application*. Also, read [this answer](https://stackoverflow.com/a/2975844/767890) – InBetween Feb 08 '18 at 22:32
  • When I started programming it as 2000. It was the age "arbitrary code excution" due to buffer overflows were rampant in Windows. NX Bits and other Forms of memory protection had not become common yet. Windows NT, 95 and DOS was still a common thing. x32 was what x64 is today. Maybe stuff changed since then. In the end Dispose and Finalizer are all about never comming into the **danger** of anything like that happening. Avoiding the issues so far, we do not have to worry what features the hardware does or does not have. – Christopher Feb 08 '18 at 22:44