I am wondering about the behavior of async/await in relation to garbage collecting local variables. In the following example, I have allocated a sizable portion of memory and go into a significant delay. As seen in the code, Buffer is not used after the await. Will it get garbage collected while waiting, or will the memory be occupied for the duration of the function?
/// <summary>
/// How does async/await behave in relation to managed memory?
/// </summary>
public async Task<bool> AllocateMemoryAndWaitForAWhile() {
// Allocate a sizable amount of memory.
var Buffer = new byte[32 * 1024 * 1024];
// Show the length of the buffer (to avoid optimization removal).
System.Console.WriteLine(Buffer.Length);
// Await one minute for no apparent reason.
await Task.Delay(60000);
// Did 'Buffer' get freed by the garabage collector while waiting?
return true;
}
Will it get garbage collected while waiting?
Maybe. The garbage collector is permitted to do so but not required to.
Will the memory be occupied for the duration of the function?
Maybe. The garbage collector is permitted to do so but not required to.
Basically, if the garbage collector can know that the buffer will never be touched again then it can free it at any time. But the GC is never required to free anything on any particular schedule.
If you are particularly concerned, you can always set the local to null, but I would not bother doing so unless you demonstrably had a problem. Alternatively, you could extract the code that manipulates the buffer into its own non-async method and call it synchronously from the async method; then the local becomes just an ordinary local of an ordinary method.
The await is realized as a return, so the local will go out of scope and its lifetime will be over; the array will then be collected on the next collection, which is required to be during the Delay, right?
No, none of those claims are true.
First, an await is only a return if the task is not completed; now, it is of course nigh impossible that Delay will be completed, so yes, this will return, but we cannot conclude in general that an await returns to the caller.
Second, the local only vanishes if it is actually realized in IL by the C# compiler as local in the temporary pool. The jitter will jit that as a stack slot or register, which vanishes when the activation for the method ends at the await. But the C# compiler is not required to do that!
It would seem strange to a person in the debugger to put a breakpoint after the Delay and see that the local has vanished, so the compiler might realize the local as a field in a compiler-generated class that is bound to the lifetime of the class generated for the state machine. In that case it is much less likely that the jitter will realize that this field is never read again, and therefore much less likely to throw it away early. (Though it is permitted to do so. And also the C# compiler is permitted to set the field to null on your behalf if it can prove that you're done using it. Again, this would be weird for the person in the debugger who suddenly sees their local change value for no apparant reason, but the compiler is permitted to generate any code whose single-threaded behaviour is correct.)
Third, nothing requires the garbage collector to collect anything on any particular schedule. This large array will be allocated on the large object heap, and that thing has its own collection schedule.
Fourth, nothing whatsoever requires there to be a collection of the large object heap in any given sixty second interval. That thing need never be collected if there is no memory pressure.
What Eric Lippert said is true: the C# compiler has quite a lot of leeway about what IL should it generate for the async method. So, if you're asking what does the specification say about this, then the answer is: the array may be eligible for collection during the wait, which means it may be collected.
But another question is what the compiler actually does. On my computer, the compiler generates Buffer as a field of generated state machine type. That field is set to the allocated array, and then it's never set again. That means the array will become eligible for collection when the state machine object does. And that object is referenced from the continuation delegate, so it won't become eligible for collection until after the wait completes. What this all means is that the array won't be eligible for collection during the wait, which means it won't be collected.
Some more notes:
The state machine object is actually a struct, but it's used though an interface it implements, so it behaves as a reference type for the purpose of garbage collection.
If you actually determine that the fact that the array won't be collected is a problem for you, it might be worth to set the local to null before the await. But in the vast majority of cases, you don't have to worry about this. I'm certainly not saying you should regularly set locals to null before await.
This is very much an implementation detail. It can change at any time and different versions of the compiler may behave differently.
Your code compiles (in my environment: VS2012, C# 5, .NET 4.5, Release mode) to include a struct that implements IAsyncStateMachine, and has the following field:
public byte[] <Buffer>5__1;
Thus, unless the JIT and/or GC are really clever, (see Eric Lippert's answer for more on that) it'd be reasonable to assume that the large byte[] will stay in scope until the async task is complete.
I'm pretty sure its collected, since await ends your current task and "continues with" another task,
so local vars should be cleaned when they are not used after the await.
BUT: What the compiler actually does could be something different, so I would not depend on such a behaviour.
There is an update on this topic with Rolsyn compiler.
Running following code in Visual Studio 2015 Update 3 in release configuration produces
True
False
So the locals are garbage collected.
private static async Task MethodAsync()
{
byte[] bytes = new byte[1024];
var wr = new WeakReference(bytes);
Console.WriteLine(wr.Target != null);
await Task.Delay(100);
FullGC();
Console.WriteLine(wr.Target != null);
await Task.Delay(100);
}
private static void FullGC()
{
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
}
Note, that if we modify MethodAsync to use local variable after await, then array buffer will not be garbage collected.
private static async Task MethodAsync()
{
byte[] bytes = new byte[1024];
var wr = new WeakReference(bytes);
Console.WriteLine(wr.Target != null);
await Task.Delay(100);
Console.WriteLine(bytes.Length);
FullGC();
Console.WriteLine(wr.Target != null);
await Task.Delay(100);
FullGC();
Console.WriteLine(wr.Target != null);
}
The output for this is
True
1024
True
True
The code samples are taken from this rolsyn issue.
Will it get garbage collected while waiting?
Nope.
will the memory be occupied for the duration of the function?
Yes.
Open your compiled assembly in Reflector. You'll see the compiler generated a private struct that inherits from IAsyncStateMachine, the local variables of your async method being a field of that struct. The data fields of the class/struct are never freed while the owning instance is still alive.
Related
I have this simple code and trying to call the destructor but I can't call it :(
I know that GarbageCollector runs when it's necessary, so I used GC.WaitForPendingFinalizers(); but it didn't work either.
Here is my code:
class Program
{
static void Main(string[] args)
{
Calculator calculator = new Calculator();
Console.WriteLine("{0} / {1} = {2}", 120, 15, calculator.Divide(120, 15)
GC.Collect();
GC.WaitForPendingFinalizers();
Console.WriteLine("Program finishing");
}
}
class Calculator
{
// Constructor
public Calculator()
{
Console.WriteLine("Calculator being created");
}
// Public Divide method
public int Divide(int first, int second)
{
return first / second;
}
// Destructor
~Calculator()
{
Console.WriteLine("Destructor is called");
}
}
And here is my output:
Calculator being created
120 / 15 = 8
Program finishing
What am I doing wrong? Why can't I see "Destructor is called" ?
The lifetime of a local variable is the lifetime of the activation of control within the local variable scope that declares it. So your local is alive until the end of main. That alone is sufficient to explain why it is not collected, but there are subtleties here that we should explore in more depth.
The lifetime may be extended by a variety of mechanisms, including capturing outer variables by a lambda, iterator blocks, asynchronous methods, and so on.
The lifetime is permitted to be shortened in cases where the jitter can prove that doing so has no effect on the single-threaded flow of control. (You can use KeepAlive to ensure this shortening does not happen in cases where you must avoid it.)
In your case, the runtime is permitted to notice that the local is never read from again, mark it as dead early, and thereby orphaning the reference to the object, which would then be collected and finalized. It is not required to do so, and apparently, in your case, does not.
As another answer correctly notes: the GC will deliberately suppress this optimization if it detects that a debugger is running, because it is a bad user experience for an object to be collected while you are examining a variable containing a reference to it in the debugger!
Let's consider the implications of my statements about shortened lifetimes, because I think you may not have fully grasped those implications.
The runtime is permitted to notice that the ctor never accesses this.
The runtime is permitted to notice that divide never accesses this.
The runtime is permitted to notice that therefore the local is never actually read from and used
Therefore the object is permitted to be never rooted in the GC at any point in its lifetime.
Which means that the garbage collector is permitted to run the finalizer before the constructor.
The GC and finalizer runs on their own threads, remember; the operating system could suspend the main thread and switch to the gc and finalizer threads at any point, including after the allocator runs but before control passes to the constructor.
Absolutely crazy things are permitted to happen in scenarios like the one you wrote; the finalizer not running is the least of your problems! It is when it could run that is scary.
If that fact was not immediately clear to you, then you have no business writing a finalizer. Writing a correct finalizer is one of the hardest things to do in C#. If you are not an expert on all the fine details of the CLR garbage collector semantics, you should not be writing a finalizer.
For more thoughts on how writing a finalizer is difficult, see my series of articles on the subject, which begins here:
https://ericlippert.com/2015/05/18/when-everything-you-know-is-wrong-part-one/
If you run a program with the debugger attached it changes the behavior of the lifetime of objects.
Without the debugger a object becomes ellagable for collection as soon as the last use of the object has been passed in the code. With the debugger attached the lifetime of all objects get extended to the entire time the object is in scope, this is done so you can view the object in the Watch window of the debugger and not have the object collected out from under you.
You must either run your program in release mode without the debugger attached or set calculator to null before you call GC.Collect() to be able to have the object be eligible for garbage collection and have it's finalizer run.
I would not recommend to really on destructors .net
anyway in your case GC don't think your object is garbage at the moment you calling GS because you have alive link in your stack calculator which is point to object in heap
so you can try to modify this code
main(){
DoCalculations();
//at this point object calculator is garbage (because it was allocated in stack)
GC.Collect();
}
DoCalculations(){
Calculator calculator = new Calculator(); // object allocated
calcualtor.doSomething(); //link alive
}
In C#, this is the standard code for invoking an event in a thread-safe way:
var handler = SomethingHappened;
if(handler != null)
handler(this, e);
Where, potentially on another thread, the compiler-generated add method uses Delegate.Combine to create a new multicast delegate instance, which it then sets on the compiler-generated field (using interlocked compare-exchange).
(Note: for the purposes of this question, we don't care about code that runs in the event subscribers. Assume that it's thread-safe and robust in the face of removal.)
In my own code, I want to do something similar, along these lines:
var localFoo = this.memberFoo;
if(localFoo != null)
localFoo.Bar(localFoo.baz);
Where this.memberFoo could be set by another thread. (It's just one thread, so I don't think it needs to be interlocked - but maybe there's a side-effect here?)
(And, obviously, assume that Foo is "immutable enough" that we're not actively modifying it while it is in use on this thread.)
Now I understand the obvious reason that this is thread-safe: reads from reference fields are atomic. Copying to a local ensures we don't get two different values. (Apparently only guaranteed from .NET 2.0, but I assume it's safe in any sane .NET implementation?)
But what I don't understand is: What about the memory occupied by the object instance that is being referenced? Particularly in regards to cache coherency? If a "writer" thread does this on one CPU:
thing.memberFoo = new Foo(1234);
What guarantees that the memory where the new Foo is allocated doesn't happen to be in the cache of the CPU the "reader" is running on, with uninitialized values? What ensures that localFoo.baz (above) doesn't read garbage? (And how well guaranteed is this across platforms? On Mono? On ARM?)
And what if the newly created foo happens to come from a pool?
thing.memberFoo = FooPool.Get().Reset(1234);
This seems no different, from a memory perspective, to a fresh allocation - but maybe the .NET allocator does some magic to make the first case work?
My thinking, in asking this, is that a memory barrier would be required to ensure - not so much that memory accesses cannot be moved around, given the read is dependent - but as a signal to the CPU to flush any cache invalidations.
My source for this is Wikipedia, so make of that what you will.
(I might speculate that maybe the interlocked-compare-exchange on the writer thread invalidates the cache on the reader? Or maybe all reads cause invalidation? Or pointer dereferences cause invalidation? I'm particularly concerned how platform-specific these things sound.)
Update: Just to make it more explicit that the question is about CPU cache invalidation and what guarantees .NET provides (and how those guarantees might depend on CPU architecture):
Say we have a reference stored in field Q (a memory location).
On CPU A (writer) we initialize an object at memory location R, and write a reference to R into Q
On CPU B (reader), we dereference field Q, and get back memory location R
Then, on CPU B, we read a value from R
Assume the GC does not run at any point. Nothing else interesting happens.
Question: What prevents R from being in B's cache, from before A has modified it during initialisation, such that when B reads from R it gets stale values, in spite of it getting a fresh version of Q to know where R is in the first place?
(Alternate wording: what makes the modification to R visible to CPU B at or before the point that the change to Q is visible to CPU B.)
(And does this only apply to memory allocated with new, or to any memory?)+
Note: I've posted a self-answer here.
This is a really good question. Let us consider your first example.
var handler = SomethingHappened;
if(handler != null)
handler(this, e);
Why is this safe? To answer that question you first have to define what you mean by "safe". Is it safe from a NullReferenceException? Yes, it is pretty trivial to see that caching the delegate reference locally eliminates that pesky race between the null check and the invocation. Is it safe to have more than one thread touching the delegate? Yes, delegates are immutable so there is no way that one thread can cause the delegate to get into a half-baked state. The first two are obvious. But, what about a scenario where thread A is doing this invocation in a loop and thread B at some later point in time assigns the first event handler? Is that safe in the sense that thread A will eventually see a non-null value for the delegate? The somewhat surprising answer to this is probably. The reason is that the default implementations of the add and remove accessors for the event create memory barriers. I believe the early version of the CLR took an explicit lock and later versions used Interlocked.CompareExchange. If you implemented your own accessors and omitted a memory barrier then the answer could be no. I think in reality it highly depends on whether Microsoft added memory barriers to the construction of the multicast delegate itself.
On to the second and more interesting example.
var localFoo = this.memberFoo;
if(localFoo != null)
localFoo.Bar(localFoo.baz);
Nope. Sorry, this actually is not safe. Let us assume memberFoo is of type Foo which is defined like the following.
public class Foo
{
public int baz = 0;
public int daz = 0;
public Foo()
{
baz = 5;
daz = 10;
}
public void Bar(int x)
{
x / daz;
}
}
And then let us assume another thread does the following.
this.memberFoo = new Foo();
Despite what some may think there is nothing that mandates that instructions have to be executed in the order that they were defined in the code as long as the intent of the programmer is logically preserved. The C# or JIT compilers could actually formulate the following sequence of instructions.
/* 1 */ set register = alloc-memory-and-return-reference(typeof(Foo));
/* 2 */ set register.baz = 0;
/* 3 */ set register.daz = 0;
/* 4 */ set this.memberFoo = register;
/* 5 */ set register.baz = 5; // Foo.ctor
/* 6 */ set register.daz = 10; // Foo.ctor
Notice how the assignment to memberFoo occurs before the constructor is run. That is valid because it does not have any unintended side-effects from the perspective of the thread executing it. It could, however, have a major impact on other threads. What happens if your null check of memberFoo on the reading thread occurred when the writing thread just fininished instruction #4? The reader will see a non-null value and then attempt to invoke Bar before the daz variable got set to 10. daz will still hold its default value of 0 thus leading to a divide by zero error. Of course, this is mostly theoretical because Microsoft's implementation of the CLR creates a release-fence on writes that would prevent this. But, the specification would technically allow for it. See this question for related content.
I think I have figured out what the answer is. But I'm not a hardware guy, so I'm open to being corrected by someone more familiar with how CPUs work.
The .NET 2.0 memory model guarantees:
Writes cannot move past other writes from the same thread.
This means that the writing CPU (A in the example), will never write a reference to an object into memory (to Q), until after it has written out contents of that object being constructed (to R). So far, so good. This cannot be re-ordered:
R = <data>
Q = &R
Let's consider the reading CPU (B). What is to stop it reading from R before it reads from Q?
On a sufficiently naïve CPU, one would expect it to be impossible to read from R without first reading from Q. We must first read Q to get the address of R. (Note: it is safe to assume that the C# compiler and JIT behave this way.)
But, if the reading CPU has a cache, couldn't it have stale memory for R in its cache, but receive the updated Q?
The answer seems to be no. For sane cache coherency protocols, invalidation is implemented as a queue (hence "invalidation queue"). So R will always be invalidated before Q is invalidated.
Apparently the only hardware where this is not the case is the DEC Alpha (according to Table 1, here). It is the only listed architecture where dependent reads can be re-ordered. (Further reading.)
Capturing reference to immutable object guarantees thread safety (in sense of consistency, it does not guarantee that you get the latest value).
List of events handlers are immutable and thus it is enough for thread safety to capture reference to current value. The whole object would be consistent as it never change after initial creation.
Your sample code does not explicitly state if Foo is immutable, so you get all sorts of problems with figuring out whether the object can change or not i.e. directly by setting properties. Note that code would be "unsafe" even in single-threaded case as you can't guarantee that particular instance of Foo does not change.
On CPU caches and like: The only change that can invalidate data at actual location in memory for true immutable object is GC's compaction. That code ensures all necessary locks/cache consistency - so managed code would never observe change in bytes referenced by your cached pointer to immutable object.
When this is evaluated:
thing.memberFoo = new Foo(1234);
First new Foo(1234) is evaluated, which means that the Foo constructor executes to completion. Then thing.memberFoo is assigned the value. This means that any other thread reading from thing.memberFoo is not going to read an incomplete object. It's either going to read the old value, or it's going to read the reference to the new Foo object after its constructor has completed. Whether this new object is in the cache or not is irrelevant; the reference being read won't point to the new object until after the constructor has completed.
The same thing happens with the object pool. Everything on the right evaluates completely before the assignment happens.
In your example, B will never get the reference to R before R's constructor has run, because A does not write R to Q until A has completed constructing R. If B reads Q before that, it will get whatever value was already in Q. If R's constructor throws an exception, then Q will never be written to.
C# order of operations guarantees this will happen this way. Assignment operators have the lowest precedence, and new and function call operators have the highest precedence. This guarantees that the new will evaluate before the assignment is evaluated. This is required for things like exceptions -- if an exception is thrown by the constructor then the object being allocated will be in an invalid state and you don't want that assignment to occur regardless of whether you're multithreaded or not.
It seems to me you should be using see this article in this case. This ensures the compiler doesn't perform optimisations that assume access by a single thread.
Events used to use locks, but as of C# 4 use lock-free synchronisation - I'm not sure exactly what (see this article).
EDIT:
The Interlocked methods use memory barriers which will ensure all threads read the updated value (on any sane system). So long as you perform all updates with Interlocked you can safely read the value from any thread without a memory barrier. This is the pattern used in the System.Collections.Concurrent classes.
I've been reading about GC in C# and I find some statements quite amusing to be real.
Say I have the following method:
public void Foo()
{
Animal eAnimal = new Animal();
eAnimal.Name = "Matthew";
GC.Collect();
...more code not using eAnimal...
return;
}
Supposedly in the GC.Collect() statement, the GC figures out that the memory address corresponding the the new Animal() object is not longer referenced in the code and thus is appropriate to dispose.
As far as I know, even in the GC.Collect() statement, there's a reference to the new Animal() in the stack (even maybe stored in some CPU registry). I know that if I inspect the code although that value exists in the stack, in the logic of the code is not longer used, but this kind of analysis I guess could be done in the compilation time and not really in runtime.
How does the GC knows that even if there's a reference in the stack to the heap, that reference it isn't gonna be used in any way in future statements of the program?.
My best guess is that the compilation takes this into account and creates appropriate instructions that cleans that reference in the stack and then makes the GC job more easy. But this seems to add a lot of overhead to the generated code.
Edit: I'm reading a book about the exam 70-483 and it states:
StreamWriter stream = File.CreateText(“temp.dat”);
stream.Write(“some data”);
GC.Collect()
When running this piece of code in Release mode, the garbage collector will see that there
are no more references to stream, and it will free any memory associated with the Stream-
Writer instance.
You're not returning a reference to that object from your method, so when you call GC.Collect(), it looks at where the object is being used and sees that nothing else will reference it after the collection call. As you indicated, none of the code after the call uses the object, so GC will safely dispose of it.
The MSDN Example does a fine job of explaining a similar situation to what you have.
A comprehensive explanation of garbage collection operation: http://msdn.microsoft.com/en-us/magazine/bb985010.aspx
The following code is a simplified example of an issue I am seeing. This application consumes approx 4GB of memory before throwing an exception as the dictionary is too big.
class Program
{
static void Main(string[] args)
{
Program program = new Program();
while(true)
{
program.Method();
Console.ReadLine();
}
}
public void Method()
{
WasteOfMemory memory = new WasteOfMemory();
Task tast = new Task(memory.WasteMemory);
tast.Start();
}
}
public class WasteOfMemory
{
public void WasteMemory()
{
Dictionary<string, string> aMassiveList = new Dictionary<string, string>();
try
{
long i = 0;
while (true)
{
aMassiveList.Add(i.ToString(), "I am a line of text designed to waste space.... I am exceptionally useful........");
i++;
}
}
catch(Exception e)
{
Console.WriteLine("I have broken myself");
}
}
}
This is all as expected, although what we cannot currently work out is when this memory should be released from the CLR.
We have let the task complete and then simulated a memory overload situation, but the memory consumed by the dictionary is not released. As the OS is running out of memory, is it not putting pressure on the CLR to release the memory?
However and even more confusing, if we wait until the task has completed, then hit enter to run the task again the memory is released, so obviously the previous dictionary has been garbage collected (hasn't it?).
So, why is the memory not being released? And how can we get the CLR to release the memory?
Any explanations or solutions would be greatly appreciated.
EDIT: Following replies, particularly Beska's, it is obvious my description of the issue is not the the clearest, so I will try to clarify.
The code may not be the best example, sorry! It was a quick crude piece of code to try to replicate the issue.
The dictionary is used here to replicate the fact we have a large custom data object, which fills a large chunk of our memory and it is not then released after the task has completed.
In the example, the dictionary fills up to the limit of the dictionary and then throws an exception, it does NOT keep filling forever! This is well before our memory is full, and it does not cause an OutOfMemoryException. Hence the result is a large object in memory, and then the task completes.
At this point we would expect the dictionary to be out of scope, as both the task and the method 'Method' have completed. Hence, we would expect the dictionary to be garbage collected and the memory reclaimed. In reality, the memory is not freed until 'Method' is called again, creating a new WasteOfMemory instance and starting a new task.
Hopefully that will clarify the issue a bit
The garbage collector only frees locations in memory that are no longer in use that are objects which have no pointer pointing to them.
(1) your program runs infinitely without termination and
(2) you never change the pointer to your dictionary, so the GC has certainly no reason to touch the dictionary.
So for me your program is doing exactly what it is supposed to do.
Okay, I've been following this...I think there are a couple issues, some of which people have touched on, but I think not answering the real question (which, admittedly, took me a while to recognize, and I'm not sure I'm answering what you want even now.)
This is all as expected, although what we cannot currently work out is when this memory should be released from the CLR.
As others have said, while the task is running, the dictionary will not be released. It's being used. It gets bigger until you run out of memory. I'm pretty sure you understand this.
We have let the task complete and then simulated a memory overload situation, but the memory consumed by the dictionary is not released. As the OS is running out of memory, is it not putting pressure on the CLR to release the memory?
Here, I think, is the real question.
If I understand you correctly, you're saying you set this up to fill up memory. And then, after it crashes (but before you hit return to start a new task) you're trying other things outside of this program, such as running other programs in Windows to try to get the GC to collect the memory, right? Hoping that the OS would talk to the GC, and start pressuring it to do it's thing.
However and even more confusing, if we wait until the task has completed, then hit enter to run the task again the memory is released, so obviously the previous dictionary has been garbage collected (hasn't it?).
I think you answered your own question...it has not been necessarily been released until you hit return to start a new task. The new task needs memory, so it goes to the GC, and the GC happily collects the memory from the previous task, which has now ended (after throwing from full memory).
So, why is the memory not being released? And how can we get the CLR to release the memory?
I don't know that you can force the GC to release memory. Generally speaking, it does it when it wants (though some hacker types might know some slick way to force its hand.) Of course, .NET decides when to run the GC, and since nothing is happening while the program is just sitting there, it may well be deciding that it doesn't need to. As to whether the OS can pressure the GC to run, it seems from your tests the answer is "no". A bit counter-intuitive perhaps.
Is that what you were trying to get at?
The memory is not being released because the scope aMassiveList is never finished. When a function returns, it releases all non-referenced resources created inside it.
In your case, aMassiveList never leaves context. If you want your function never to return you have to find a way to 'process' your info and release it instead of storing all of them forever.
If you create a function that increasingly allocates resources and never release it you will end up consuming all the memory.
GC will only release unreferenced objects, so as the dictionary is being referenced by your program it can't be released by the GC
The way you've written the WasteMemory method, it will never exit (unless the variable "i" overflows, which won't happen this year) and BECAUSE IT WILL NEVER EXIT it will keep IN USE the reference to the internal Dictionary.
Daniel White is right, you should read about how GC works.
If the references are in use, GC will not collect the referenced memory. Otherwise, how would any program work?
I don't see what you expect the CLR/GC to do here. There's nothing to garbage-collect inside one run of your WasteMemory method.
However and even more confusing, if we wait until the task has completed, then hit enter to run the task again the memory is released, so obviously the previous dictionary has been garbage collected (hasn't it?).
When you press Enter, a new task is created and started. It's not the same task, it's a new task - a new object holding a reference to a new WasteOfMemory instance.
The old task will keep running and the memory it uses will NOT be collected because the old task keeps running in background and it keeps USING that memory.
I'm not sure why - and most importantly HOW - you observe the memory of the old task being released.
Change your method to be a using statement
Example:
Using (WateOfMemory memory = new WateOfMemory())
{
Task tast = new Task(memory.WasteMemory);
tast.Start();
}
And add disposible WateOfMemoryClass (by the way your constructor is WasteOfMemory)
#region Dispose
private IntPtr handle;
private Component component = new Component();
private bool disposed = false;
public WateOfMemory()
{
}
public WateOfMemory(IntPtr handle)
{
this.handle = handle;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if(!this.disposed)
{
if(disposing)
{
component.Dispose();
}
CloseHandle(handle);
handle = IntPtr.Zero;
}
disposed = true;
}
[System.Runtime.InteropServices.DllImport("Kernel32")]
private extern static Boolean CloseHandle(IntPtr handle);
~WateOfMemory()
{
Dispose(false);
}
#endregion
I am a bit confused about the fact that in C# only the reference types get garbage collected.
That means GC picks only the reference types for memory de-allocation.
So what happens with the value types as they also occupy memory on stack ?
For a start, whether they're on the stack or part of the heap depends on what context they're part of - if they're within a reference type, they'll be on the heap anyway. (You should consider how much you really care about the stack/heap divide anyway - as Eric Lippert has written, it's largely an implementation detail.)
However, basically value type memory is reclaimed when the context is reclaimed - so when the stack is popped by you returning from a method, that "reclaims" the whole stack frame. Likewise if the value type value is actually part of an object, then the memory is reclaimed when that object is garbage collected.
The short answer is that you don't have to worry about it :) (This assumes you don't have anything other than the memory to worry about, of course - if you've got structs with references to native handles that need releasing, that's a somewhat different scenario.)
I am a bit confused about the fact that in C# only the reference types get garbage collected.
This is not a fact. Or, rather, the truth or falsity of this statement depends on what you mean by "get garbage collected". The garbage collector certainly looks at value types when collecting; those value types might be alive and holding on to a reference type:
struct S { public string str; }
...
S s = default(S); // local variable of value type
s.str = M();
when the garbage collector runs it certainly looks at s, because it needs to determine that s.str is still alive.
My suggestion: clarify precisely what you mean by the verb "gets garbage collected".
GC picks only the reference types for memory de-allocation.
Again, this is not a fact. Suppose you have an instance of
class C { int x; }
the memory for the integer will be on the garbage-collected heap, and therefore reclaimed by the garbage collector when the instance of C becomes unrooted.
Why do you believe the falsehood that only the memory of reference types is deallocated by the garbage collector? The correct statement is that memory that was allocated by the garbage collector is deallocated by the garbage collector, which I think makes perfect sense. The GC allocated it so it is responsible for cleaning it up.
So what happens with the value types as they also occupy memory on stack ?
Nothing at all happens to them. Nothing needs to happen to them. The stack is a million bytes. The size of the stack is determined when the thread starts up; it starts at a million bytes and it stays a million bytes throughout the entire execution of the thread. Memory on the stack is neither created nor destroyed; only its contents are changed.
There are too many verbs used in this question, like destroyed, reclaimed, deallocated, removed. That doesn't correspond well with what actually happens. A local variable simply ceases to be, Norwegian parrot style.
A method has a single point of entry, first thing that happens is that the CPU stack pointer is adjusted. Creating a "stack frame", storage space for the local variables. The CLR guarantees that this space is initialized to 0, not otherwise a feature you use strongly in C# because of the definite assignment rule.
A method has a single point of exit, even if you method code is peppered with multiple return statements. At that point, the stack pointer is simply restored to its original value. In effect it "forgets" that the local variables where ever there. Their values are not 'scrubbed' in any way, the bytes are still there. But they won't last long, the next call in your program are going to overwrite them again. The CLR zero-initialization rule ensures that you can never observe those old values, that would be insecure.
Very, very fast, takes no more than a single processor cycle. A visible side-effect of this behavior in the C# language is that value types cannot have a finalizer. Ensuring no extra work has to be done.
A value type on the stack is removed from the stack when it goes out of scope.
Value types are destroyed as soon as they go out of scope.
value types would get deallocated when the stack frame is removed after it has been executed i would assume
Would also like to add that stack is at a thread level, and heap is at application domain level.
So, when a thread ends it would reclam the stack memory used by that specific thread.
Every value type instance in .NET will be part of something else, which could be a larger enclosing value type instance, a heap object, or a stack frame. Whenever any of those things come into being, any structures within them will also come into being; those structures will then continue to exist as long as the thing containing them does. When the thing that contains the structure ceases to exist, the structure will as well. There is no way to destroy a structure without destroying the container, and there is no way to destroy something that contains one or more structures without destroying the structures contained therein.