Is it bad practice to depend on the .NET automated garbage collector?
Asked Answered
I

3

6

It's possible to create lots of memory-intensive objects and then abandon references to them. For example, I might want to download and operate on some data from a database, and I will do 100 separate download and processing iterations. I could declare a DataTable variable once, and for each query reset it to a new DataTable object using a constructor, abondoning the old DataTable object in memory.

The DataTable class has easy built-in ways to release the memory it uses, including Rows.Clear() and .Dispose(). So I could do this at the end of every iteration before setting the variable to a new DataTable object. OR I could forget about it and just let the CLR garbage collector do this for me. The garbage collector seems to be pretty effective so the end result should be the same either way. Is it "better" to explicitly dispose of memory-heavy objects when you don't need them, (but add code to do this) or just depend on the garbage collector to do all the work for you (you are at the mercy of the GC algorithm, but your code is smaller)?

Upon request, here is code illustrating the recycled DataTable variable example:

    // queryList is list of 100 SELECT queries generated somewhere else.
    // Each of them returns a million rows with 10 columns.
    List<string> queryList = GetQueries(@"\\someserver\bunch-o-queries.txt");
    DataTable workingTable;

    using (OdbcConnection con = new OdbcConnection("a connection string")) {
        using (OdbcDataAdapter adpt = new OdbcDataAdapter("", con)) {
            foreach (string sql in queryList) {
                workingTable = new DataTable();  // A new table is created. Previous one is abandoned
                adpt.SelectCommand.CommandText = sql;
                adpt.Fill(workingTable);
                CalcRankingInfo(workingTable);
                PushResultsToAnotherDatabase(workingTable);
                // Here I could call workingTable.Dispose() or workingTable.Rows.Clear()
                // or I could do nothing and hope the garbage collector cleans up my
                // enormous DataTable automatically.
            }   
        }
    }
Invalidate answered 22/9, 2011 at 20:14 Comment(10)
The answer to your question (the one in the title) is NO. Show your code for more detailed answer. If you don't depend in the .NET GC you are definitely doing something wrong.Gianna
This question hinges on "explicitly dispose of memory-heavy objects". Which is not possible in .NET, only the garbage collector can do that. IDisposable has nothing to do with memory.Seifert
@Hans Passant, +1 for IDisposable has nothing to do with memory.Gianna
@HansPassant What do you mean it has nothing to do with memory? (Honest question) I though that's what it was for. If not--what is it for?Invalidate
@jmh_gr, without a specific example we cannot be constructive.Gianna
IDisposable helps to release unmanaged resources early. Memory is the one resource that is not unmanaged.Seifert
@DarinDimitrov I posted an example now. So are you saying that Dispose() doesn't actually clear up the memory used by the table? What about Rows.Clear()?Invalidate
Bad example. DataTable.Dispose() is a dummy like that of memoryStream.Inexcusable
Please refer to my answer at [#4268229 [1]: #4268229Overstep
@HansPassant Why is memory not unmanaged? What if unmanaged parts reserve / require memory, e.g. native c++ code within your managed app? This memory should be cleaned up during the dispose, I think.Ore
O
7

Ok, time to clear things up a bit (since my original post was a little muddy).

IDisposable has nothing to do with Memory Management. IDisposable allows an object to clean up any native resources it might be holding on to. If an object implements IDisposable, you should be sure to either use a using block or call Dispose() when you're finished with it.

As for defining memory-intensive objects and then losing the references to them, that's how the Garbage Collector works. It's a good thing. Let it happen and let the Garbage Collector do its job.

...so, to answer your question, No. It is not a bad practice to depend on the .NET Garbage Collector. Quite the opposite in fact.

Oralle answered 22/9, 2011 at 20:17 Comment(3)
A pity you continue the link between Dispose() and Memory management. IDisposable is about resources, only in very rare (and questionable) cases about memory.Inexcusable
@Henk - Already edited the response to mention that Dispose() has nothing to do with Managed Memory Management).Oralle
Maybe I'm just focusing on semantics here but IDisposable is (though indirectly) related to memory management. Yes, the true purpose of IDisposable is to proactively release resources so that you don't put strain on the GC. However, I cannot think (off the top of my head) of a single resource that does not have an impact on an applications memory consumption/utilization. Do some debugging with WinDbg and you'll see what I'm referring to.Overstep
O
8

@Justin

...so, to answer your question, No. It is not a bad practice to depend on the .NET Garbage Collector. Quite the opposite in fact.

It is a horrible practice to rely on the GC to cleanup for you. It's unfortunate that you are recommending that. Doing so, can very likely lead you down the path of have a memory leak and yes, there are at least 22 ways you can "leak memory" in .NET. I've worked at a huge number of clients diagnosing both managed and unmanaged memory leaks, providing solutions to them, and have presented at multiple .NET user groups on Advanced GC Internals and how memory management works from the inside of the GC and CLR.

@OP: You should call Dispose() on the DataTable and explicitly set it equal to null at the end of the loop. This explicitly tells the GC that you are done with it and there are no more rooted references to it. The DataTable is being placed on the LOH because of its large size. Not doing this can easily fragment your LOH resulting in an OutOfMemoryException. Rememeber that the LOH is never compacted!

For additional details, please refer to my answer at

What happens if I don't call Dispose on the pen object?

@Henk - There is a relationship between IDisposable and memory management; IDisposable allows for an semi-explicit release of resources (if implemented correctly). And resources always have some sort of managed and typically unmanaged memory associated with them.

A couple of things to note about Dispose() and IDisposable here:

  1. IDisposable provides for disposal of both Managed and Unmanaged memory. Disposal of Unmanaged memory should be done in the Dispose Method and you should provide a Finalizer for your IDisposable implementation.

  2. The GC does not call Dispose for you.

  3. If you don't call Dispose(), the GC sends it to the Finalization queue, and ultimately again to the f-reachable queue. Finalization makes an object survive 2 collections, which means it will be promoted to Gen1 if it was in Gen0, and to Gen2 if it was in Gen1. In your case, the object is on the LOH, so it survives until a full GC (all generations plus the LOH) is performed twice which, under a "healthy" .NET app, a single full collection is performed approx. 1 in every 100 collections. Since there is lots of pressure on the LOH Heap and GC, based on your implementation, full GC's will fire more often. This is undesirable for performance reasons since full GC's take much more time to complete. Then there is also a dependency on what kind of GC you're running under and if you are using LatencyModes (be very careful with this). Even if you're running Background GC (this has replaced Concurrent GC in CLR 4.0), the ephemeral collection (Gen0 and Gen1) still blocks/suspends threads. Which means no allocations can be performed during this time. You can use PerfMon to monitor the behavior of the memory utilization and GC activity on your app. Please note that the GC counters are updated only after a GC has taken place. For additional info on versions of GC, see my response to

    Determining which garbage collector is running.

  4. Dispose() immediately releases the resources associated with your object. Yes, GC is non-deterministic, but calling Dispose() does not trigger a GC!

  5. Dispose() lets the GC know that you are done with this object and its memory can be reclaimed at the next collection for the generation where that object lives. If the object lives in Gen2 or on the LOH, that memory will not be reclaimed if either a Gen0 or Gen1 collection takes place!

  6. The Finalizer runs on 1 thread (regardless of version of GC that is being used and the number of logical processors on the machine. If you stick alot in the Finalization and f-reachable queues, you only have 1 thread processing everything ready for Finalization; your performance goes you know where...

For info on how to properly implement IDisposable, please refer to my blog post:

How do you properly implement the IDisposable pattern?

Overstep answered 7/12, 2011 at 17:4 Comment(12)
"Dispose() lets the GC know". The GC is oblivious to calls to Dispose.Genera
Ok, I should clarify: Dispose() does not "call" the GC and the GC never calls Dispose(). What I meant to say was that when Dispose() has been called, the GC operates on a set of memory that has been put into a "non-referenced" state - i.e. removing rooted referernces and preventing object promotion into the next GC generation. In other words, calling Dispose() has a direct correlation to the work that the GC has to do.Overstep
"the GC operates on a set of memory that has been put into a "non-referenced" state - i.e. removing rooted referernces and preventing object promotion into the next GC generation". Are you talking about classes with an implementation of Dispose that explicitly removes references or the general case of any Disposable? AFAIK, the GC is oblivious to calls to Dispose so calling Dispose will not, in general, have any effect at all on promotion to the next generation (which is dictated entirely by reachability).Genera
I'm talking about how the GC works, irrespective of IDisposable. The GC operates on managed objects that are reference types (typically placed on the Heap) - regardless of whether or not they implement IDisposable. A class will/should implement IDisposable when it is necessary to cleanup resources (ultimately freeing memory assoc with the resource). response cont'd below...Overstep
IDisposable allows for you do avoid the 15-20 potential memory leaks you can have in .NET and reduce GC pressure (which has only a single Finalizer thread). Calling Dispose can cleanup the memory so the GC only "sees less things" to cleanup - it doesn't know anything about the calls to Dispose() that "gave it" less work to do...Overstep
Dispose() can have an effect on promotion and reachability. I can think of 4 examples: first, you can explicitly remove rooted references by setting the resource to 'null' (and yes, it makes a difference - proven by WinDbg. There can be certain transaction state variables that are, for instance, on the "this" pointer which are still reachable). Second you can unregister/detach event handlers in Dispose(). Remember that these are also rooted references. Third, if you don't call Dispose() when using a COM object (using Marshal.ReleaseComObject()) the memory will be pinned..cont'd belowOverstep
and thus cannot be collected and therefore promoted to the next generation. Fourth, calling SuppressFinalize() in a Dispose implementation will stop a Finalizer from being called. If a Finalizer does not need to be called, the object will not be placed into the f-reachable queue, and thus not be promoted since it will take at least another full collection to have it collected.Overstep
"Dispose() can have an effect on promotion and reachability". Yes. All code can have an effect on promotion and reachability. The characteristics you describe apply to all code and have nothing specifically to do with Dispose.Genera
@JonHarrop: If an object overrides Finalizer and implements IDisposable, then 99.44% of the time, the method IDisposable.Dispose() will call GC.SuppressFinalize(this) which will in turn let the garbage-collector know no finalization cleanup is required. While Dispose() might not call GC.SuppressFinalize(), and other methods might, in practice, GC.SuppressFinalize() usually called by IDisposable.Dispose(), and not by anything else.Astoria
@Astoria I agree that the correlation exists. My point is that it is not a causal relationship. Dispose does not "let the GC know" any more than the next bit of code lets the GC know.Genera
It's all semantics. Does it contact the GC and tell it anything? No. But what it does do is to allow for a proper cleanup affecting the change the state of an object with managed or unmanaged resources. When the GC comes wanting to collect for a generation in which this object lives, it will examine the state that the call to Dispose() has put it in to decide to clean it up or not.Overstep
@JonHarrop - your quote of "promotion to the next generation (which is dictated entirely by reachability)" is not entirely correct. Any class that has a Finalizer is immediately promoted by 1 Generation because of the way it works with the finalization and f-reachable queues. I urge you to verify this with WinDbg as it is trivial to do.Overstep
O
7

Ok, time to clear things up a bit (since my original post was a little muddy).

IDisposable has nothing to do with Memory Management. IDisposable allows an object to clean up any native resources it might be holding on to. If an object implements IDisposable, you should be sure to either use a using block or call Dispose() when you're finished with it.

As for defining memory-intensive objects and then losing the references to them, that's how the Garbage Collector works. It's a good thing. Let it happen and let the Garbage Collector do its job.

...so, to answer your question, No. It is not a bad practice to depend on the .NET Garbage Collector. Quite the opposite in fact.

Oralle answered 22/9, 2011 at 20:17 Comment(3)
A pity you continue the link between Dispose() and Memory management. IDisposable is about resources, only in very rare (and questionable) cases about memory.Inexcusable
@Henk - Already edited the response to mention that Dispose() has nothing to do with Managed Memory Management).Oralle
Maybe I'm just focusing on semantics here but IDisposable is (though indirectly) related to memory management. Yes, the true purpose of IDisposable is to proactively release resources so that you don't put strain on the GC. However, I cannot think (off the top of my head) of a single resource that does not have an impact on an applications memory consumption/utilization. Do some debugging with WinDbg and you'll see what I'm referring to.Overstep
W
1

I also agree with Dave's post. You should always dispose and release your database connections, even if the framework you are working has documentation that it is not needed.

As a DBA who has worked with MS SQL, Oracle, Sybase/SAP, and MYSQL, I have been brought in to work on mysterious locking and memory leaking that was blamed on the database when in fact, the issue was because the developer did not close and destroy their connection objects when they were done with them. I've even seen apps that leave idle connections open for days and it can really make things bad when your database is clustered, mirrored, and with Always on recovery groups in SQL Server 2012.

When I took my first .Net class the instructor taught us to only keep database connections open while you are using them. Get in, get your work done and get out. This change has made several systems I have help optimize a lot more reliable. It also frees up connection memory in the RDBMS giving more ram to buffer IO.

Wheels answered 13/11, 2014 at 14:57 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.