Stack and Heap Memory in .NET

What Happens Internally When We Declare a Variable in a .NET Application?

When we declare a variable in a .NET application, it allocates some memory in the RAM. The memory that it allocates in RAM has three things are as follows:

  1. Name of the Variable,

  2. The Data Type of the Variable, and

  3. Value of the Variable.

For a better understanding, please have a look at the following image. Here, we declare a variable of type int and assign a value 101.

What happens internally when we declare a variable in .NET Application?

The above image shows a high-level overview of what is happening in the memory. But depending on the data type (i.e., depending on the value type and reference type ), the memory may be allocated either in the stack or in the heap memory.

Understanding Stack and Heap Memory in C#:

There are two types of memory allocation for the variables we created in the .NET Application, i.e., Stack Memory and Heap Memory.

Understanding Stack and Heap Memory in .NET

As you can see in the above image, the SomeMethod has three statements. Let’s understand statement by statement how things are executed internally.

Statement 1:

In C#, when the first statement is executed, memory is allocated on the stack for the variables and data structures needed during the execution of that statement. The stack is a region of memory that is used to keep track of method calls and local variables. The stack memory is responsible for keeping track of the running memory (refers to the portion of RAM that is actively being used by a running program) needed in your application. It includes the memory occupied by the program's instructions, variables, and data structures during its execution. For a better understanding, please have a look at the following image.

Stack in .NET Application with Examples

Statement 2:

When the second statement is executed, it stacks this memory allocation (memory allocation for variable y) on top of the first memory allocation (memory allocation for variable x). You can think about the stack as a series of plates or dishes put on top of each other. Please have a look at the following diagram for a better understanding.

Stack memory in C#

The Stack Memory allocation and de-allocation in .NET uses the Last In, First Out Principle. In other words, we can say that the memory allocation and de-allocation are done only at one end of the memory, i.e., the top of the stack.

Statement3:

In the 3rd statement, we have created an object of SomeClass. When the 3rd statement is executed, it internally creates a pointer on the stack memory, and the actual object is stored in a different memory location called Heap memory. The Heap Memory location does not track running memory. Heap is used for dynamic memory allocation. For a better understanding, please have a look at the below image.

Heap Memory in .NET Application

Note: The reference pointers are allocated on the stack. The statement, SomeClass cls1, does not allocate any memory for an instance of SomeClass. It only allocates a variable with the name cls1 in the stack and sets its value to null. When it hits the new keyword, it allocates memory in the heap.

What Happens When the Method Completes Its Execution?

When the three statements are executed, the control will exit from the method. When it passes the end control, i.e., the end curly brace “},” it will clear all the memory variables created on the stack. It will de-allocate the memory from the stack in a ‘LIFO’ fashion. For a better understanding, please have a look at the below image.

What happens to stack and Heap memory when the method complete its execution?

It will not de-allocate the Heap memory. Later, the heap memory will be de-allocated by the garbage collector. Now, you may have one question in your mind: why two types of memory? Can’t we allocate everything to just one memory type?

Why do we have two types of memory?

In C#, primitive data types, such as int, double, bool, etc., hold a single value. On the other hand, the reference data types or object data types are complex, i.e., an object data type or reference data type can have reference to other objects and other primitive data types.

So, the reference data type holds references to other multiple values, and each one of them must be stored in memory. Object types need dynamic memory, while primitive data types need static memory. Please have a look at the following image for a better understanding.

Why we have two types of memory (Stack and Heap in .NET)?

Value Types and Reference Types in C#.NET

In C# ,the Value types are the types that hold both data and memory in the same location. On the other hand, a reference type is a type that has a pointer that points to the actual memory location.

Understanding Value Type in C#:

As you can see in the image, first, we create an integer variable with the name x, and then we assign this x integer value to another integer variable named y. In this case, the memory allocation for these two variables will be done inside the stack memory.

Understanding Value Type in .NET

In .NET, when we assign one integer variable value to another integer variable, it creates a completely different copy in the stack memory. So, if you change one variable value, the other variable will not be affected. In .NET, these data types are called Value types. So, bool, byte, char, decimal, double, enum, float, long, sbyte, int, short, ulong, struct, uint, ushort are examples of value types.

Understanding Reference Type in C#:

Here, first, we create an object, i.e., obj1) and then assign this object to another object, i.e., obj2. In this case, both reference variables (obj1 and obj2) will point to the same memory location.

Understanding Reference Type in C#

In this case, when you change one of them, the other object is also affected. These kinds of data types are termed Reference types in .NET. So, class, interface, object, string, and delegate are examples of Reference Types.

How is the Heap Memory Freed Up?

The memory allocation on the stack is deallocated when the control moves out from the method, i.e., once the method completes its execution. On the other hand, the memory allocation, which is done on the heap, needs to be de-allocated by the garbage collector.

When an object stored on the heap is no longer used, that means the object does not have any reference pointing. Then, the object is eligible for garbage collection. The garbage collector will de-allocate this object from the heap at some point.

Key Points for Stack Memory Management
  1. only local variables

  2. resizing of variables cannot be done (Static/Fixed size)

  3. efficiently managed space by CPU, memory does not become fragmented

  4. very quick access

  5. there is a limit on stack size (OS-dependent)

explicit de-allocation of variables is not mandatory

Key Points for Heap Memory Management
  1. memory must be managed (explicit deallocation is needed).
    It isn’t managed by CPU or any onboard mechanism.

  2. memory size is not limited (very large size)

  3. access is relatively slower (because data is not stored sequentially like in stack and is randomly anywhere in heap and hence first needed to be found before reading, plus data gets fragmented also. This makes it inefficient and increasingly slower if not managed properly.)

  4. realloc() is used for the resizing of variables (dynamic)

  5. variables can be accessed globally

efficient use of space is not guaranteed, memory can become fragmented over time when blocks of memory are allocated, then freed.

Why heap is not managed by CPU but Stack is managed ?

The stack and heap serve different purposes in memory management, and their management strategies are designed to meet the requirements of their respective use cases. While the stack is managed by the CPU, the heap is typically managed by the operating system or a memory allocator library. Here are some reasons why the heap is not managed directly by the CPU:

  1. Dynamic Memory Allocation:

    • The heap is used for dynamic memory allocation, where the size and lifetime of memory blocks are not known at compile time. This contrasts with the stack, which is used for managing local variables with known sizes and lifetimes.

    • Dynamic memory allocation on the heap involves more complex operations, such as searching for free memory blocks and managing their allocation and deallocation over time. This is better suited for higher-level memory management mechanisms provided by the operating system or memory allocator libraries.

  2. Memory Fragmentation:

    • Dynamic memory allocation on the heap can lead to memory fragmentation over time. Managing memory fragmentation requires more sophisticated algorithms and strategies, making it a task better suited for higher-level memory management systems
  3. Operating System Control:

    • The heap is often managed by the operating system, which can handle the allocation and deallocation of memory blocks on a broader scale. The operating system can implement memory protection mechanisms and handle issues like virtual memory, ensuring the isolation and security of different processes.
  4. Heap Memory Allocation Strategies:

    • Heap memory allocation involves strategies like first-fit, best-fit, or worst-fit, which are better handled by dedicated memory management systems rather than the CPU. These strategies require knowledge of the entire heap and are not easily implemented within the CPU itself.