When we create an instance of a normal class, the memory of that instance lives on the heap.
A reference to this instance might be in :
- An object on the heap.
- If we set a member variable inside a different instance of an object to it.
- A stack variable
- If we declared a variable to the object inside a method or passed it to a function call.
- It may be in the list of global roots,
- for static reference.
Static classes are not instantiated but are “stored” on what is called a Loader Heap. Its methods are just functions loaded into memory when the CLR loads the assembly. Static classes are guaranteed to be loaded and to have their fields initialized and their static constructor called before the class is referenced for the first time in your program. Once created a static class remains in memory until your application domain is shut down.
Loader heaps are special, non-GC heaps that have extremely predictable and strict growth rates. A reference to every type, static or otherwise, is held on a loader heap. The entire type system is managed on a loader heap, with references to types and their members.
When a .NET application starts, several AppDomains are actually created. In addition to the primary app domain, there are system and shared app domains that contain the system namespaces and mscorelib, special heaps (such as the loader heaps), and the CLR itself.
For a fully detailed explanation, read the following MSDN Magazine article:
Drill Into .NET Framework Internals to See How the CLR Creates Runtime Objects