Inconsistent Garbage Collection

HI All,

I’m pretty stumped. I’m trying to hunt down a memory leak related to our display (garbage collection is manually triggered using GC.Collect() when memory is low - we’ve run into issues with events and timers throwing out of memory exceptions while GC is running). We’ve narrowed it down to certain on screen elements, but because, if I’m not mistaken, the display buffer is a fixed size, so I don’t understand why it cause a leak when certain regions are overwritten.

However, in an attempt to determine where the memory leak is by running GC before and after blocks of code to monitor memory usage over time, I’ve found that this memory leak only appears when running garbage collection infrequently - all the memory clears out just fine if I run the GC on time intervals of less than about 8 seconds.

Is there something under the hood in GC that would cause this behavior? I thought running GC every loop should still manifest a memory leak - it would just be tiny, but I’d be able to track growing memory usage over time. However, this only shows if GC runs only when needed.

GC response after about a minute of triggering the leak - Binary_Blob_Head is growing rapidly
GC Initialized
GC: 319msec 15034352 bytes used, 18520000 bytes available
Type 11 (STRING ): 63968 bytes
Type 13 (CLASS ): 5451152 bytes
Type 14 (VALUETYPE ): 36336 bytes
Type 15 (SZARRAY ): 76128 bytes
Type 01 (BOOLEAN ): 96 bytes
Type 03 (U1 ): 44240 bytes
Type 04 (CHAR ): 464 bytes
Type 07 (I4 ): 768 bytes
Type 0E (R8 ): 80 bytes
Type 11 (STRING ): 496 bytes
Type 13 (CLASS ): 27536 bytes
Type 14 (VALUETYPE ): 2448 bytes
Type 17 (FREEBLOCK ): 18520000 bytes
Type 19 (ASSEMBLY ): 40960 bytes
Type 1B (REFLECTION ): 96 bytes
Type 1D (DELEGATE_HEAD ): 3456 bytes
Type 1E (DELEGATELIST_HEAD ): 416 bytes
Type 1F (OBJECT_TO_EVENT ): 384 bytes
Type 20 (BINARY_BLOB_HEAD ): 6358032 bytes
Type 21 (THREAD ): 1600 bytes
Type 22 (SUBTHREAD ): 192 bytes
Type 23 (STACK_FRAME ): 1840 bytes
Type 24 (TIMER_HEAD ): 320 bytes
Type 29 (FINALIZER_HEAD ): 2984320 bytes
Type 33 (IO_PORT ): 336 bytes
Type 36 (APPDOMAIN_HEAD ): 80 bytes
Type 38 (APPDOMAIN_ASSEMBLY ): 14736 bytes

GC Method -

 public static void RunGC(int loopcnt)
        {
                try
                {
                if(loopcnt % 100 == 0 || Helpers.freeBytes < 4000000)
                {
                    GC.Collect();
                    //lets debugger know that the GC has been run
                    Debug.WriteLine(" GC Initialized");
                }
                
                }
                catch (Exception e)
                {
                    Debug.WriteLine(e.Message);
                }
        }

Finalizers maybe? See this

System.GC.Collect();
System.GC.WaitForPendingFinalizers();

How do you know you have a leak? Do you run out of memory eventually? Memory is very dynamic and things can go higher and lower over time.

I monitor memory capacity in real time. It starts at about ~30 MB and gradually runs out, triggering the garbage collector, maybe once a minute or so. This is all well and good, but the issue starts when one of these on screen elements is shown. Each time the garbage collector runs, it recovers less and less memory until memory completely runs out and the GC can’t recover any more.

Update, the WaitForPendingFinalizers() call seems to keep it under control but it takes about 10 seconds for GC to run. What exactly does that function wait for? If I know what calls would cause finalizers to take forever, it would help narrow down exactly what is cause this behavior.

That is normal and it is expected. This is when the GC runs and collect the garbage left behind. But if the GC runs and you are still out of memory then you have a memory leak, which is very rare in managed systems.

This is what’s happening.

Nothing! It sleeps your thread giving the system time to breath and clear all unneeded objects.

Cool. Well this gives me somewhere to start at least. It’s still creating about 4 mb worth of unnecessary objects but it’s not crashing anymore. Thanks for the assist.

My guess is there’s something in the code creating more objects than the GC can keep up with.

1 Like

It sleeps the current (foreground) thread. What about event threads, like timer, serial receive, CAN receive ???

It doesn’t.

A healthy system design would not use 100% processor 100% of the time. You should not need to call any of these functions but they are there if needed.