Garbage collection and memory management.
Ημερομηνία: 09/04/2014
Δεν έχετε συμπληρώσει όλα τα απαιτούμενα στοιχεία
Το email που δώσατε δεν είναι σωστό
Garbage collection is an essential part of .Net architecture since it saves you from the trouble of managing the memory parts you have already used. We are going to see what garbage collection actually is and what may cause it. Next we'll check out how we can manage memory parts ourselves and talk about the disposed pattern.
Introduction
Garbage collection was one of the first things I heard of when started learning .Net. Even so, it's been some time since then that I got the first idea of what garbage collection is about, as most developers were not interested in how it works. Since garbage collection and memory allocation is not a part of a typical .Net developer and "it ain't broke" it doesn't sound like it "needs to be fixed". However a developer who likes to know how things work, may find this article quite interesting, as it forms a simple description of what happens under the hood while our programs keep running.
The stack and the heap
This is an older article where I talk about memory allocation in detail. Let's just go through the basics and see how things are stored in memory, so we can see how things are removed from memory later on.
A .Net application gets a part of the available memory and uses it as its own. This part is called virtual memory and even though it can be much larger in 64-bit systems than mere 4GB in 32-bit systems, it will always be limited.
Information is stored in two parts of the virtual memory, called the stack and the heap. The stack stores value data types. On the contrary, the heap stores reference data types.
Supposing we wanted to create an integer value
int int1 = 0;
that value would be stored to the stack.
Now, if we wanted to create a class object
ReferenceTypeClass object1= new ReferenceTypeClass();
we would create a reference to the stack pointing to some certain point of the heap where we would store the actual object info.
Actually the heap consists of two types, the small object heap and the large object heap. Objects are by default stored in the small object heap unless they are larger than 85000 bytes, in which case they are stored in the large object heap. These types of heaps are no different to the garbage collector so we will refer to both in the same way; heap.
Stack memory allocation
So, information is stored both on the stack and the heap. Since the stack seems to be simpler than the stack, let's do this first.
Stack values are stored the way a simple stack would. From bottom to top. However the stack can only take up to 1 MB of data. Exceeding this limit will result in StackOverflow and I guess it is quite clear at that point why this exception is called that way.
To avoid this endless info flow .Net uses a stack pointer. This is nothing more than a simple pointer to the next available memory place. So, if I create some integer, the stack pointer will rise by 4 bytes etc. However when a variable stored in the stack goes out of scope, the memory where it's value was stored is no longer useful and the stack pointer will fall down to the place where our integer was stored. There, it will wait till a new variable is created and overwrite the old integer's value. That way, the same memory parts will be used again and again thus causing the StackOverflow far less possible to be thrown.
A variable goes out of scope when it can no longer be used in our source code. Here's an example.
bool getValueFromCookie = GetValueFromCookie();
int unencryptedValue = 0;
if(getValueFromCookie)
{
HttpCookie cookie = Request.Cookies["EncryptedValue"];
if(cookie != null)
{
int encryptedValue = (int)cookie["EncryptedValue"]
unencryptedValue = UnencryptInteger(encryptedValue);
}
}
int newInteger = 0;
We have stored an encrypted integer within a cookie. To get it back we check if getValueFromCookie is true, and then we use UnencryptInteger to unencrypt the stored value. This example contains three value types. getValueFromCookie, unencryptedValue and encryptedValue. However the encryptedValue's scope extends far less than the other two variables'. This is because encryptedValue exists only within the if(cookie != null) brackets. When the code reaches the closing bracket the stack pointer will move to the point where the encryptedValue is stored in the stack. The next command, (int newInteger = 0;) will cause this new variable to be placed on that position.
Heap memory allocation - Garbage collection
The heap (also known as managed heap) uses its own pointer, the heap pointer. In contrast to the stack, the heap will keep moving the heap pointer to the next free memory part when a new object is created. Using the same way as the stack to move the heap pointer up and down would be no good as, due to the objects' size diversity, it wouldn't be easy for the heap pointer to find the position suitable to all new objects. As a result the heap pointer always points to the next available memory part.
Repeating the same procedure for many objects the heap pointer would eventually reach the end of the heap. In order to avoid this, CLR will cause a garbage collection when the heap pointer surpasses an accepting threshold (this threshold can be adjusted while the application runs). Garbage collection will probably clear some part of the heap and allow the application to move on. Here's how it works.
When an object gets out of scope its connection between the stack and the heap is lost, much like it would happen if we had set that object to null. When garbage collection is initialized it searches for objects whose connections are dropped. (We could also say that garbage collection searches for objects without roots. Roots represent the location of the objects in the heap.) The memory these objects required is released. When this procedure is completed, the remaining parts are combined so that they form a solid part of the memory. The heap pointer is then placed on top of that part waiting for the next object to be stored.
In the following image there are two examples of garbage collection. In the first one object1 is set to null. In the second, it is object11 that is set to null.
Keep in mind you can initialize the garbage collection yourselves by calling System.GC.Collect. However you are not advised to do so as the garbage collector and your application cannot both run at the same time. So, unless you are really sure there are many objects that need to be removed right now, (and not when CLR thinks it's time to do so) you may chose to do so.
Generations
Some interesting part concerning garbage collections is the way it separates older from younger objects. The heap is separated into three sections called generations. Generation 0, 1 and 2.
When an object is created, it is placed into the generation 0 section. The heap gets filled with objects this way until the generation 0 threshold is reached. This is when garbage collection is initialized. When the garbage collection is over, all survival objects will be compacted and moved to the generation 1 section.
At the moment the generation 0 section is empty. When the next garbage collection shows up, the new remaining objects will be transferred to generation 1 section and all remaining objects formerly in generation 1 will be moved to generation 2 section. The story goes on the same way. Objects stored in generation 2 section surviving the garbage collection will simply be compacted in the same section until the time comes when they will be released.
Destroying objects
Most people have heard that every class apart from the very popular constructor may have a destructor, even though most of them haven't had the chance to use it. A destructor is nothing more than a method named after ~Class_Name. This method can only be called automatically when an object is destroyed (removed from the heap) or the program terminates. For example when an object is destroyed, you may want to change some values in your class. Behind the scenes, a destructor will fire the Finalize method, which is mostly used in languages like C++. That's why the destructors are also called finalizers by some developers.
Here's an example where we see a simple destructor in action.
protected void Page_Load(object sender, EventArgs e)
{
TerminatingClass.status = "";
UseGarbageCollector();
//Show results
ProgressLiteral.Text = TerminatingClass.status;
}
private void UseGarbageCollector()
{
TerminatingClass t = new TerminatingClass();
//Set t to null so it does not survive garbage collection
t = null;
System.GC.Collect();
//Wait for two seconds so the garbage collection is completed
Thread.Sleep(2000);
}
class TerminatingClass : IDisposable
{
static public string status;
public TerminatingClass()
{
status += "Constructor";
}
~TerminatingClass()
{
status += " - Destructor";
}
}
We will use a string static variable to keep track of which methods are called.
First we create a TerminatingClass object. Then it is set to null and we cause garbage collection. (If t was not null, it would have survived the garbage collection). Then we wait two seconds for the garbage collection to finish and the destructors to be called.
The result in our page is
Constructor - Destructor
as expected.
Now here's a nice quote I've heard concerning garbage collection: "Garbage collection is simulating a computer with an infinite amount of memory". What this means is that even though your computer may have limited memory resources, the fact that the garbage collection allows you to use the same resources again and again makes it seem like there's no end (unless you get OutOfMemory or StackOverflow exceptions). So, what the garbage collection does is manage the memory when such a thing has to be done. Supposing we had created a destructor expecting to work for some object of ours. In case the amount of memory was large enough the garbage collector might never occur. So, presuming that garbage collection will always occur at least once is not accurate.
When an object whose class contains a destructor is created an instance is also created in the Finalize queue pointing that when this object is about to be removed from memory there are some things that need to be done first. When such an object is about to be removed through garbage collection, it is not; instead due to the Finalize queue the destructor code will be executed. Next time the garbage collection occurs, is when that object will be removed. Keeping that in mind we can tell that creating destructors may slow down the garbage collection.
Considering that a destructor cannot be called whenever you want among all that we have said, you can see why destructors are not much used any more. Instead most developers have turned to the disposed pattern.
Disposing of objects
A class implementing the IDisposable interface, must contain a Dispose method. This method can either be used directly or by placing the object within a using statement.
Dispose is generally used to get rid of resources connected to an objected. For example instead of writing
SqlConnection con = new SqlConnection(connectionString);
con.Close();
we could write
SqlConnection con = new SqlConnection(connectionString);
con.Dispose();
as SqlConnection's Dispose method will call the Close method itself.
"using" is a keyword which allows a reference variable to be used within (but not outside) the following brackets.
For example
using (SqlConnection con = new SqlConnection())
{
// do stuff
}
Here's the catch. Dispose will now be called when this object goes out of scope, even if an exception occurs. That way you avoid the try catch you would need before using con.Close() or con.Dispose()
An example follows on, similar to the previous, showing how a dispose method works.
protected void Page_Load(object sender, EventArgs e)
{
TerminatingClass.status = "";
DisposeObject();
//Show results
ProgressLiteral.Text = TerminatingClass.status;
}
private void DisposeObject()
{
using (TerminatingClass t = new TerminatingClass())
{
//use t object to do stuff
}
//some_object.Dispose() works as well
//TerminatingClass t = new TerminatingClass();
//t.Dispose();
}
class TerminatingClass : IDisposable
{
static public string status;
public TerminatingClass()
{
status += "Constructor";
}
public void Dispose()
{
status += " - Dispose";
}
}
Now the result is
Constructor - Dispose
Destructors or Dispose?
A destructor is not the same as calling Dispose method. A destructor will be called by garbage collector, Dispose will be called either directly or when out of scope. When an object is disposed it is not removed from memory. Calling Dispose will do nothing more than calling dispose.
A combination of the previous methods
TerminatingClass t = new TerminatingClass();
t.Dispose();
t = null;
System.GC.Collect();
//Wait for two seconds so the garbage collection is completed
Thread.Sleep(2000);
will result in
Constructor - Dispose - Destructor
As said, a disposed object is not removed from memory. That is what the garbage collection looks after.
Using Dispose seems to be a better way to handle such issues than using destructors, However you may choose to use both methods should you wish to cover all possible cases.
Summary
Garbage collection is the way .Net releases no more needed memory resources and is initialized when the CLR thinks it is needed. We can create destructors containing code that will run when the object is about to be removed from memory. Implementing the IDisposable interface, classes may use the Dispose method to release resources much easier.
Πίσω στο BlogΠροηγούμενοΕπόμενο