Objects lifetime
Objects lifetime
Subjects:
- Classes, objects, and references
-
The basics of object lifetime
-
The role of application roots
-
Understanding object generations
-
The System.GC type
-
Building finalizable objects
-
Building disposable objects
Subjects:
- Classes, objects, and references
-
The basics of object lifetime
-
The role of application roots
-
Understanding object generations
-
The System.GC type
Classes, objects, and references
Classes, objects, and references
It is important to further clarify the distinction between classes, objects, and references.
public class Car
{
private int currSp;
private string petName;
public Car(){}
public Car(string name, int speed) {
petName = name; currSp = speed; }
public override string ToString() {
return string.Format("{0} is going {1} MPH", petName, currSp);
Classes, objects, and references
Classes, objects, and references
Once a class is defined, you can allocate any number of objects using the C# new keyword.
class Program
{
static void Main(string[] args) {
// Create a new Car object on the managed heap. We are
// returned a reference to this object ("refToMyCar").
Car refToMyCar = new Car("Zippy", 50);
// The C# dot operator (.) is used to invoke members
// on the object using our reference variable.
Console.WriteLine(refToMyCar.ToString()); Console.ReadLine();
Memory management
Memory management
The .NET Framework CLR (Common Language Runtime) frees the developer from the burden of managing memory (allocating and freeing up when done); it handles memory management itself by detecting when memory can be safely freed.
Memory is allocated to instantiations of .NET types (objects) from the managed heap,
a pool of memory managed by the CLR. As long as there exists a reference to an object, which might be either a direct reference to an object or via a graph of objects, the object is considered to be in use.
When there is no reference to an object, and it cannot be reached or used, it
becomes garbage, eligible for collection. .NET Framework includes a garbage collector which runs periodically, on a separate thread from the application's thread, that enumerates all the unusable objects and reclaims the memory allocated to them.
Dynamic memory allocation (also known as heap-based memory allocation) is the allocation of memory storage for use in a computer program during the runtime of that program. It can be seen also as a way of distributing ownership of limited memory resources among many pieces of data and code.
The basics of object lifetime
The basics of object lifetime
When you are building your C# applications, you are correct to assume that the managed heap will take care of itself without your direct intervention. In fact, the golden rule of .NET memory management is simple:
Rule. Allocate an object onto the managed heap using the new keyword and forget about it. Once instantiated, the garbage collector will destroy the object when it is no longer needed. The next obvious question, of course, is, “How does the garbage collector determine when an object is no longer needed?” The short answer is that the garbage collector removes an object from the heap when it is unreachable by any part of your code base.
static void MakeACar() {
// If myCar is the only reference to the Car object, // it *may* be destroyed when this method returns. Car myCar = new Car();
}
Notice that the Car reference (myCar) has been created directly within the
The role of
application roots
The role of
application roots
A root is a storage location containing a reference to an object on the heap. Strictly speaking, a root can fall into any of the following categories:
- References to global objects.
- References to any static objects/static fields.
- References to local objects within an application’s code base. - References to object parameters passed into a method.
- References to objects waiting to be finalized. - Any CPU register that references an object.
During a garbage collection process, the runtime will investigate objects on the managed heap to determine whether they are still reachable (aka rooted) by the application. To do so, the CLR will build an object graph,
which represents each reachable object on the heap.
Understanding object generations
Understanding object generations
To help optimize the process, each object on the heap is assigned to a specific “generation.” The idea behind generations is simple: the longer an object has existed on the heap, the more likely it is to stay there.
- Generation 0: Identifies a newly allocated object that has never been marked for collection.
- Generation 1: Identifies an object that has survived a garbage collection (i.e., it was marked for collection, but was not removed due to the fact that the sufficient heap space was acquired).
- Generation 2: Identifies an object that has survived more than one sweep of the garbage collector.
The garbage collector will investigate all generation 0 objects first. If marking and sweeping these objects results in the required amount of free memory, any surviving objects are promoted to generation 1.
If all generation 0 objects have been evaluated, but additional memory is still required, generation 1 objects are then investigated for their “reachability”
The
System.GC
type
The
System.GC
type
The base class libraries provide a class type named System.GC that allows you to programmatically interact with the garbage collector using a set of static members.
System.GC member Meaning in life
AddMemoryPressure() RemoveMemoryPressure()
Allow you to specify a numerical value that represents the calling object’s “urgency level” regarding the garbage collection process. Be aware that these methods should alter pressure in tandem and thus never remove more pressure than the total amount you have added.
Collect() Forces the GC to perform a garbage collection. This method has been overloaded to specify a generation to collect, as well as the mode of collection (via the GCCollectionMode enumeration).
CollectionCount() Returns a numerical value representing how many times a given generation has been swept.
GetGeneration() Returns the generation to which an object currently belongs.
GetTotalMemory() Returns the estimated amount of memory (in bytes) currently allocated on the managed heap. The Boolean parameter specifies whether the call should wait for garbage collection to occur before returning.
MaxGeneration Returns the maximum of generations supported on the target system. Under Microsoft’s .NET 3.5, there are three possible generations (0, 1, and 2).
SuppressFinalize() Sets a flag indicating that the specified object should not have its Finalize() method called.
The
System.GC
type
The
System.GC
type
Whole purpose of the .NET garbage collector is to manage memory on our behalf. However, under some very rare circumstances, it may be beneficial to programmatically force a garbage collection using GC.Collect(). Specifically:
- your application is about to enter into a block of code that you do not wish to be interrupted by a possible garbage collection;
- your application has just finished allocating an extremely large number of objects and you wish to remove as much of the acquired memory as possible.
static void Main(string[] args) {
...
// Force a garbage collection and wait for // each object to be finalized.
GC.Collect();
GC.WaitForPendingFinalizers(); ...
}
The
System.GC
type
The
System.GC
type
static void Main(string[] args) {
// Print out estimated number of bytes on heap. Console.WriteLine("Estimated bytes on heap: {0}", GC.GetTotalMemory(false));
// MaxGeneration is zero based.
Console.WriteLine("This OS has {0} object generations.\n", (GC.MaxGeneration + 1)); Car refToMyCar = new Car("Zippy", 100);
Console.WriteLine(refToMyCar.ToString()); // Print out generation of refToMyCar.
Console.WriteLine("\nGeneration of refToMyCar is: {0}", GC.GetGeneration(refToMyCar)); // Make a ton of objects for testing purposes.
object[] tonsOfObjects = new object[50000];
for (int i = 0; i < 50000; i++) tonsOfObjects[i] = new object(); // Collect only gen 0 objects.
GC.Collect(0, GCCollectionMode.Forced); GC.WaitForPendingFinalizers();
// Print out generation of refToMyCar.
Console.WriteLine("Generation of refToMyCar is: {0}", GC.GetGeneration(refToMyCar)); // See if tonsOfObjects[9000] is still alive.
if (tonsOfObjects[9000] != null) {
Console.WriteLine("Generation of tonsOfObjects[9000] is: {0}", GC.GetGeneration(tonsOfObjects[9000])); } else Console.WriteLine("tonsOfObjects[9000] is no longer alive.");
// Print out how many times a generation has been swept. Console.WriteLine("\nGen 0 has been swept {0} times", GC.CollectionCount(0));
Console.WriteLine("Gen 1 has been swept {0} times", GC.CollectionCount(1));
Console.WriteLine("Gen 2 has been swept {0} times", GC.CollectionCount(2));
Building finalizable objects
Building finalizable objects
You learned that the base class of .NET, System.Object, defines a virtual method named Finalize(). The default implementation of this method does nothing whatsoever: // System.Object
public class Object
{
protected virtual void Finalize() { } }
When you override Finalize() for your custom classes, you establish a specific location to perform any necessary cleanup logic for your type. Given that this member is defined as protected, it is not possible to directly call an object’s Finalize() method from a class instance via the dot operator. Rather, the garbage collector will call an object’s
Finalize() method (if supported) before removing the object from memory.
public class MyResourceWrapper {
// Compile-time error!
protected override void Finalize(){ } }
Rather, when you wish to configure your custom C# class types to override the
Finalize() method, you make use of a (C++-like) destructor syntax to achieve the same effect.
// override System.Object.Finalize() via finalizer // syntax.
class MyResourceWrapper
{
~MyResourceWrapper() {
// Clean up unmanaged resources here.
// Beep when destroyed (testing purposes only!) Console.Beep();
Building disposable objects
Building disposable objects
Finalizers can be used to release unmanaged resources when the garbage collector kicks in. However, given that many unmanaged objects are “precious items” (such as database or file handles), it may be valuable to release them as soon as possible instead of relying on a garbage collection to occur. As an alternative to overriding Finalize(), your class could implement the IDisposable interface, which defines a single method named
Dispose(): public interface IDisposable
{
void Dispose(); }
Here is an updated MyResourceWrapper class that now implements
IDisposable, rather than overriding System.Object.Finalize():
// Implementing IDisposable.
public class MyResourceWrapper : IDisposable {
// The object user should call this method // when they finish with the object.
public void Dispose() {
// Clean up unmanaged resources...
// Dispose other contained disposable objects... // Just for a test.
Console.WriteLine("***** In Dispose! *****"); }
}