1

What should I do when I have situations as the one below, where I'd need to clean an object and immediately free its allocated memory?

After creating a List<string> with 10 million words, process memory goes up to ~150MB.

List<string> list = new();
int length = 10000000;
for (int i = 0; i < length; i++)
{
    list.Add("test");
}

Console.ReadLine();
list.Clear();
Console.ReadLine();

Even though the list is cleared, I don't see memory being freed just after that. Could anyone give me some guidance on this, please?

Mureinik
  • 297,002
  • 52
  • 306
  • 350
roccaforte
  • 81
  • 5

2 Answers2

3

List.Clear just replaces all of the copies of references to each of the cleared strings with nulls. So the actual buffer in the list is exactly the same size, the values in it are all just zeros. Since the list was just full of references to a single string in the literal pool, none of the objects referenced by the list have become eligible for collection (the one object that used to be in the list is still alive because it's in the string intern pool, which is rooted).

To actually clear out the internal list buffer you'll either need to remove all references to the list itself, or set a smaller Capacity on the list to have a new, smaller, buffer created, allowing the old one to be collected.

Servy
  • 202,030
  • 26
  • 332
  • 449
  • Got it! Thanks! 1) But what happens if instead of having a single string "test" I replace it by a List of random decimals? I suppose there wouldn't be something equivalent to the 'string intern pool' you mentioned that would hold these random decimals, right? 2) How do I remove all references to the list itself? – roccaforte Dec 03 '21 at 21:04
  • 1
    @roccaforte Then you'd be changing a bunch of random values to zeros, and no memory would be freed. The same would be true for any other type of value type. The only way for `Clear` to result in any freed memory is if there are reference types in the list that are not accessible from any other rooted reference. In such a case the list's buffer would still stay rooted and use up a lot of memory, but the memory of all of the referenced objects would be able to be freed eventually. In that case your test would use way more memory until clearing, and then would use the same after clearing. – Servy Dec 03 '21 at 21:11
  • got it! Thank you. Much appreciated. – roccaforte Dec 03 '21 at 22:02
2

You can't control when C# actually frees its memory. Once you've Cleared a list all the items that used to be in it are eligible to be garbage-collected (assuming nothing else holds a reference to them, as in the given example), but you can't control when exactly C#'s garbage collector will actually free them.

Mureinik
  • 297,002
  • 52
  • 306
  • 350
  • Ok, I see. Thanks. Even though I can't control it, isn't there a way to force it to run whenever I wish? – roccaforte Dec 03 '21 at 19:46
  • 2
    Why you need it? You have performance issues with memory? Memory is bottleneck of your app? Do your code, let GC do its job itself. Imagine your list is a room. You fill room with people, then people leave. Room still remains and available for new people. You asking to "how can I delete room". – Auditive Dec 03 '21 at 19:53
  • Nice parallel, but in this case, my understanding is that people leave the room when I run the Clear method, but they are still somewhere in the house. Why they don't just leave it? Answering your question: yes, I need to keep a few GBs in memory for ~45 seconds and then upload other datasets of about the same size... And the process goes on and on. – roccaforte Dec 03 '21 at 20:01
  • 2
    "I need to keep a few GBs in memory" - I believe there is better way to do what you doing now. Maybe you should ask for "how can I optimize my current code" and provide a code example, so wise people in room of StackOverflow would kindly help you with advices. – Auditive Dec 03 '21 at 20:08
  • 1
    I mean, for example, instead of loading whole 1GB file with `File.ReadAllText` you can use FileStream+StreamReader and keep memory OK. – Auditive Dec 03 '21 at 20:13
  • 2
    When memory is needed, the garbage collector will decide to free or reuse memory. Garbage collection is an expensive operation so it will run when it can and do what it has to do. You shouldn't worry about this. – David Libido Dec 03 '21 at 20:19
  • @Auditive. I will study FileStream+StreamReader. Thanks for helping. – roccaforte Dec 03 '21 at 20:24
  • Nothing about the code in question has anything to do with the garbage collector. Nothing becomes eligible for collection as a result of clearing the list, even after a full GC collect. This is not an issue of forcing collection or not, the issue is that this code is *still holding onto lots of memory* and keeping it alive, despite the apparent desire for it to be freed. – Servy Dec 03 '21 at 20:49
  • @roccaforte What *would* become eligible for collection here? The list itself is no smaller, the only object removed from the list is lots of copies of a string literal that's still in the intern pool. What is now eligible for collection after calling `Clear` in this code? – Servy Dec 03 '21 at 20:59
  • @Servy I'd say the Object itself, which is the List in this case. I'm not sure how to remove if though, as I just mentioned in the quite clear answer you've just posted. – roccaforte Dec 03 '21 at 21:07
  • 1
    @roccaforte While I can understand why you might think it would, but it does not you can see the implementation of `List.Clear` to see that it does not free the internal buffer, just sets all of the values to 0. – Servy Dec 03 '21 at 21:09