Yet another GC question

I would be happy if someone will be able to answer the following question.

recently I was investigating memory leaks & handles problem during my work. I have seen a very strange phenomenon: I have a class that exposes only static methods. on one of the methods I do the following things

  1. I create an start thread
  2. The ThreadStart delegate calls a method that call GC.Collect() & GC.WaitForPendingFinalizers().

Most of the time the new thread never finishes it's work because it gets stuck in GC.WaitForPendingFinalizers(). The only scenario that this thread is resumed is when I activate another GC collection from my program's console menu.

The whole phenomenon doesn't happen if I make the following changes:

  1. I use gcThread.IsBackground = false; on the thread that invokes the GC
  2. I use gcThread.Join() in the method that creates this thread.

I have tried to reproduce the problem with some toy example but I couldn't.

  1. Can somebody explain my mistake
  2. Is this a GC bug
  3. Can somebody reproduce the problem with some toy application

Thanks in advance



Answer this question

Yet another GC question

  • ruiner333

    1. To call GC.Collect() & WaitForPendingFinalize I create a new thread and in the TreadStart delegate I call those methods. If this thread doesn't finish after some timeout I abort this thread and print an error trace. What is happening is that in some scenario I wasn't able to reproduce this thread is always aborted. I can see in the Task Manager that my application memory is increasing and the handle count is increasing too.
    2. I have some objects that have finalizers and their finalizers are not called in this scenario. The only thing the GC do in this scenario is to move this objects from the Finalization Queue to the Freachable Queue in my opinion.
    3. Even without my finalizers the handle count is growing. When the handle count reaches beyond 100,000 I execute GC.Collect & GC.WaitForPendingFinalizers from my application menu and the handle count reaches to ~1,000. How come the GC didn't do its work in this scenario It is obvious that there was a lot of garbage.


  • d__i___o

    If your handle count is increasing, this suggests that perhaps there is some place in your code where you are creating disposable objects but you're not calling Dispose.

    Increasing handle counts are not a symptom of a problem with GC. They are a symptom of a problem in code. (The GC is not designed to free handles. It is concerned only with memory. If you are relying on the GC to free handles or other non-memory resources, then that's a bug in your code.) The only sense in which this might be a symptom of a GC problem is that it indicates you're relying on the GC to do something it (a) wasn't designed to do (free handles) and (b) does a really bad job of.

    The GC does not track handle counts. It only tracks the use of memory on the managed heap. That is all. So if you have 1000 objects each of which takes up 40 bytes on the managed heap, the CLR treats that as 40,000 bytes of memory usage. It makes no difference whether it's just plain memory and data, or whether each of these objects contains a handle to something that costs 10K per object (making the total cost 10,040,000 bytes) the CLR only cares about the managed heap. Anything else is your problem.

    (Strictly speaking in .NET 2, they added some features where objects could indicate to the CLR that they were exerting a hidden memory pressure. But in general, the CLR is only concerned with its own heap.)

    What sorts of objects are you creating in your application Do you know what these handles are Whatever they are, the fact that you're relying on the GC and finalization to free them up for you means you are in a bad place. This isn't what the GC is for, and you'll never get this approach to work properly. (Except for trivial examples, perhaps.) You need to make sure you are calling Close or Dispose on whatever objects you're using that are wrapping external resources. This is why C# (and VB.NET v2) has the 'using' construct.

    (This is a fundamental issue with garbage today's collected systems. Java has exactly the same issue - try writing a web app that doesn't call 'close' on its JDBC connection objects and see how far you scale before you run out of DB connections and grind to a halt...)

    Also, a more general rule in .NET is: if you're writing a finalizer, you're almost certainly doing the wrong thing. Finalizers are only necessary in a tiny fraction of cases. (Worse than that, finalizers turn out only to be useful in a tiny fraction of the cases where they might initially seem to be useful, which is itself a tiny fraction of overall cases...)

    It's not quite true to say that you should never write a finalizer, but it's so close to the truth that you should always regard the use of a finalizer with extreme suspicion. In a code review I'd approach a finalizer in much the same way I'd approach a goto statement: I'd want an astonishingly good reason for it to be there.


  • Baji Prasad

    Itzhak,

    What code is in your objects' finalizers If there is code in there that will not return (blocks, loops forever, etc), then the call to WaitForPendingFinalizers will not return.

    As Peter pointed out, you probably shouldn't have a thread that calls GC.Collect like that. What problem are you trying to solve

    -Chris


  • Atul Kulkarni

    Calling GC.Collect() and/or GC.WaitForPendingFinalizers() generally degrades application performance. Why do you want to call these at the start of your thread WaitForPendingFinalizers is documented as "no guarantee this method will terminate"--which sounds like what you're seeing.

  • Ying06

    Why are you calling WaitForPendingFinalizers if that's the last thing your thread does This will block the thread until the finalizers have run. It won't actually expedite the execution of those finalizers.

    Finalizers run when the finalizer thread is good and ready to run them. This usually happens fairly promptly because the finalizer thread runs with higher than default priority.

    WaitForPendingFinalizers just blocks until the queue of finalizable objects has been emptied by the finalizer thread. It has no impact on how soon the finalizer thread attempts to empty the queue. So if your thread is as you described it - you just call GC.Collect and then GC.WaitForPendingFinalizers - there's no real point in the WaitForPendingFinalizers. The only call that actually has an effect is GC.Collect.

    As for why it's getting stuck, how exactly are you determining this What are you using to detect that it has not completed

    In any case, I'm not sure how much value there is in firing off a separate thread to do this. The GC will suspend all the threads for some time anyway. (As I understand it, even a concurrent GC can only parallelize certain aspects of the GC process - certain stages still require all threads to be suspended.)

    I've tried to repro what you describe with a toy app and I can't, so it must be something peculiar to your app. Are you making much use of finalizers


  • JuliusY

    Hi Ian,

    Thanks for your help. I understand what you say. Generally, you are right.

    Best regards,

    Itzhak


  • Anand Raman - MSFT

    Hi

    1. I know that calling GC degrades performance but I have an application that consumes a lot of resources and sometimes it takes a lot of time till the GC decides to run. Therefore my application performance degrades too. To solve this problem I decided to cal the GC at specific points in my application where the GC effect will be little. I have seen that this solves the problem mentioned above.
    2. The program gets stuck when I call the GC at its beginning. So I don't this its has something to do with finalizers. Moreover, I have added debugging traces to my finalizers and I see that when they execute they end successfully.
    3. The only thing I can think of is that I don't call GC from the main thread but from some other thread which may be background or not (not as I mentioned in my previous post. The problem happens even when the other thread is not a background thread!).

    Thanks in advance


  • Yet another GC question