Classes, Objects, and References

class Car
 {
     public int CurrentSpeed { get; set; }
     public string PetName { get; set; }
     public Car()
     {
             
     }
     public Car(int speed,string petName)
     {
         CurrentSpeed = speed;
         PetName = petName;
     }
 }

If you declare the reference variable as a local variable in a method scope, it is stored stack for further use in your application.

Car refToMyCar = new Car(50,"Z");


Note: Structures are value types that are always allocated directly on the stack and are never placed on the .NET Core managed heap. Heap allocation occurs only when you are creating instances of classes.


The Basics of Object Lifetime

Rule: Allocate a class instance onto the managed heap using the new keyword and forget about it.

Once instantiated the garbage collector will destroy an object when it is no longer needed.

The garbage collector removes an object from the heap only if its unreachable by any part of your code base.

The CIL of new

When the C# compiler encounters the new keyword, it emits a CIL newobj into the method implementation.

The .NET Core garbage collector is quite a tidy housekeeper of the heap, given that it will compact empty blocks of memory (when necessary) for the purposes of optimization.

To aid in this endeavor, the managed heap maintains a pointer (commonly referred to as the next object pointer or new object pointer) that identifies exactly where the next object will be located. That said, the newobj instruction tells the runtime to perform the following core operations:

  1. Calculate the total amount of memory required for the object to be allocated.
  2. Examine the managed heap to ensure that there is indeed enough room to host the object to be allocated. If there is, the specified constructor is called, and the caller is ultimately returned a reference to the new object in memory, whose address just happens to be identical to the last position of the next object pointer.
  3. Finally, before returning the reference to the caller, advance the next object pointer to point to the next available slot on the managed heap.

Car c1 = new Car();
Car c2 = new Car();


As your application is busy allocating objects the space on the managed heap may eventually become full when processing the newobj instruction, it the runtime determines that the managed heap does not have sufficient memory to allocate the requested type, it will perform a garbage collection in an attempt to free up.

Rule: If the managed heap does not have sufficient memory to allocate a requested object, a garbage collection will occur.


Setting Object References to null

Car myCar = new Car();
myCar = null;

in Ildasm.exe would be -> IL_000XX: ldnull

Assigning a reference to null does not in any way force the garbage collector to fire up at that exact moment and remove the object from the heap. The only thing you have accomplished is explicitly clipping the connection between the reference and the object it previously pointed to.


Determining If an Object is Live

The garbage collector uses the following information to determine whether an object is live:

  1. Stack roots : Stack variables provided by the compiler and stack walker.
  2. Garbage Collection Handles: Handles that point to managed objects that can be referenced from code of the runtime.
  3. Static data : Static objects in application domains that can reference other objects.

During a garbage collection process, the runtime will investigate objects on the managed heap to determine whether they are still reachable by the application. To do so, the runtime will build an object graph, which represents each reachable object on the heap.

Just understand that object graphs are used to document all reachable objects.



Let's say C and F unreachable objects.

E depends G and B

A depends nothing.

After objects have been marked for termination (C,F), they are swept from memory. The remaining space on the heap is compacted.



Note: Strictly speaking, the gc uses 2 distinct heaps, one of which is specifically used to store large objects. This heap is less frequently consulted during the collection cycle, given possible performance penalties involved with relocating large objects. In .NET Core, the large heap can be compacted on demand or when optional hard limits for absolute or percentage memory usage is reached.