-1

I would like to ask for clarification on how .net Garbage Collector works in this case.

I have a static function in which I allocate the byte array over and over again. I am inserting byte array reference into the list. The reference to the byte array or the list is not passed outside this function.

public static void StressMem(TimeSpan duration)
{
    var bgnTm = DateTime.Now;

    var data = new List<byte[]>();

    while (true)
    {
        var arr = new byte[1024];
        data.Add(arr);

        var difTm = DateTime.Now - bgnTm;
        if (difTm > duration) break;
    }
}

I was expecting the memory to be freed (after some time) when this function was finished. But this is not happening to me. Why is this happening?

dotnet 5.0.302

Clyde
  • 311
  • 2
  • 14
  • 4
    How do you know it's not being released? How do you know if it is GC'ed? – Enigmativity Aug 05 '21 at 10:50
  • What machine and OS? x32 or x64 or other? Windows, Linux, Mac or other? What .NET implementation? Microsoft, Mono or other? From my past experience, GC behavior can be variable and works best on Windows Microsoft .NET, poorly on Linux and/or Mono, very bad on linux and rarely called even manually, even Windows Mono was poor. But that was at the beginning 20 years ago, and I don't know for nowadays, but it should be fine now, never tested again. Otherwise the Windows GC works very well and very reactive since the beginning, as I know, since Windows XP to 10 x32 or x64. –  Aug 05 '21 at 10:53
  • I monitor memory using dotnet-counters (GC Heap Size (MB)) and Task Manager/Details/My Process - Memory. GC Heap size increases, but does not decrease anymore. Memory decreases to 1/3 of the maximum when called, but it is still many times higher than the idle value. OS: Windows 10, SDK 5.0.302 – Clyde Aug 05 '21 at 11:26
  • @Clyde - Can you show your full code? Including how specifically you monitor memory? – Enigmativity Aug 05 '21 at 11:36
  • The complete code is long. It is part of the Web API project. The function is called from apiController. – Clyde Aug 05 '21 at 11:48
  • @Clyde - Sorry. I would like a [mcve] that demonstrates your issue. You don't need to post code that goes beyond minimal, but what you have posted is not sufficient. – Enigmativity Aug 06 '21 at 01:06

3 Answers3

1

The GC is in principle free to run whenever it wants, or never at all for that matter. In practice I would not expect it to run unless you are actually trying to allocating something. So after StressMem returns you might not see any GC unless you do some more work that require memory. If you ran StressMem in a loop I would expect frequent GCs.

Note that that does not necessarily mean that the memory usage for the process will decrease. The garbage collector may return memory to the OS if physical memory runs low, but is you have free memory, why not use it?

If you are investigating how your application uses memory I would recommend a memory and/or performance profiler. They should reveal how much time you are using for GC, what you are using memory for, how much you are allocating etc.

JonasH
  • 28,608
  • 2
  • 10
  • 23
  • Thank you for answer. So is it that memory is occupied, but if the system needs it, it will be freed? And if I call StressMem() in a loop, shouldn't memory continue to grow? Thank you for the advice I will examine the performance profiler. – Clyde Aug 05 '21 at 11:29
  • If you run `StressMem` in a loop, the GC will run every now an then, freeing it to be used again by the process, but not returning it to the OS. So the memory usage should plateau. – JonasH Aug 05 '21 at 11:33
0

Try running this code:

void Main()
{
    GC.Collect();
    long x = GC.GetTotalMemory(false);
    StressMem(TimeSpan.FromMilliseconds(100.0));
    long y = GC.GetTotalMemory(false);
    GC.Collect();
    long z = GC.GetTotalMemory(false);

    Console.WriteLine(x);
    Console.WriteLine(y);
    Console.WriteLine(z);
}

public static void StressMem(TimeSpan duration)
{
    var bgnTm = DateTime.Now;

    var data = new List<byte[]>();

    while (true)
    {
        var arr = new byte[1024];
        data.Add(arr);

        var difTm = DateTime.Now - bgnTm;
        if (difTm > duration) break;
    }
}

That's your exact method with some code that checks memory.

I get:

2278192
59759468
2278640

It's clearly allocating memory and the GC, when forced to run, collects it.

The GC only runs when there is pressure on memory. It isn't based on "some time".

Enigmativity
  • 113,464
  • 11
  • 89
  • 172
  • `The GC only runs when there is pressure on memory` That is perhaps overly simplistic, and may give the impression that the GC will run only when memory is low. It will (currently at least) run even if memory isn't low (based on number of allocations). Now it doesn't _promise_ to do that (true) - but your statement makes it sound like in the absence of low memory then the GC won't run (which I think is a slight stretch). – mjwills Aug 05 '21 at 13:29
  • @mjwills - I agree. – Enigmativity Aug 06 '21 at 01:07
0

If I understand correctly, GC does not release memory unless it has a reason. For example, if I make a program that takes up some memory and then does nothing else, then the process will take up memory and most likely not release it anymore (unless GC.Collect() is called directly). I tried it on a small example (see below).

public static void StressMem(TimeSpan duration)
{
   var bgnTm = DateTime.Now;

   var data = new List<byte[]>();

   while (true)
   {
      var arr = new byte[1024];
      data.Add(arr);

      var difTm = DateTime.Now - bgnTm;
      if (difTm > duration) break;
   }
}

static void Main(string[] args)
{
   var mem = GC.GetTotalMemory(false);
   Console.WriteLine($"{DateTime.Now} [Begin] {mem}");

   StressMem(TimeSpan.FromSeconds(5));
   mem = GC.GetTotalMemory(false);
   Console.WriteLine($"{DateTime.Now} [StressMem] {mem}");

   var dt = DateTime.Now;
   var lastPrint = dt;

   while (true)
   {
      var now = DateTime.Now;

      if (now - lastPrint > TimeSpan.FromSeconds(15))
      {
         lastPrint = now;

         mem = GC.GetTotalMemory(false);
         Console.WriteLine($"{DateTime.Now} {mem}");
      }

      if (now - dt > TimeSpan.FromMinutes(15)) break;
   }
}
Enigmativity
  • 113,464
  • 11
  • 89
  • 172
Clyde
  • 311
  • 2
  • 14