Variables fall into two categories: value type and reference type. It is this category that often determines where in memory they will be stored.
Value type variables store the data directly in the associated memory space, as shown in the figure above. Reference type variables store a reference to an object. If a reference type variable holds a valid reference (one that is not null), then it must be for an object that is of the type specified by the variable. While a value type variable will point to a space in memory large enough to hold the value, a reference type variable will point to a space in memory large enough to hold a reference to the object. Regardless of how large the object is, that reference will occupy a fixed size, for example, 4 bytes in a 32-bit Windows machine.

The diagram above shows a string reference variable. In this example, the address of the object is stored on the stack. The object itself with the string “hello world”, is stored on the heap.
Memory Structure
When an application is run, each process is allocated a block of virtual memory space to use. On a 32-bit computer, an application has 2GB of virtual address space, which is shared by these processes. These virtual memory addresses do not map directly to the physical memory. This mapping is handled by the page table, a structure which the system maintains for each process. Virtual memory is used as it provides application isolation, with each in their own address space. This means one application can write to and read from their virtual block of memory, without worrying about the possible impact on other applications.

This virtual memory space is split into multiple segments: Text, Initialised Data, Uninitialised Data, Stack, and the Heap. In this article, we are mostly concerned with the Stack and Heap segments. However, the Text, Initialised, and Uninitialised Data segments are briefly outlined below.

Text Segment
The text segment (also known as the code segment) is an area of the virtual memory that contains executable instructions. This segment cannot be changed by the application and is therefore read-only and of a fixed size. It is worth noting that any const instance variables are replaced inline by the compiler and are stored with the code here.
Initialised Data Segment
Also known as the data segment, this portion of the virtual memory stores the global variables that have been initialised by the developer. This segment is not read-only as the values can be changed at runtime.
Uninitialised Data Segment
Also known as the BSS (Block Started by Symbol) segment, this portion of the virtual memory stores uninitialised data. Again, this segment is read-only as the values must be set at runtime.
Stack Segment
The stack is responsible for keeping track of code execution. It does this using stack frames, which contain all the data for a single function call.
Heap
The heap is a shared area of virtual memory used for dynamic allocations.
The following code sample highlights where different types of variables are stored.
Boxing and Unboxing
Wow, you have given so much knowledge, so what’s the use of it in actual programming? One of the biggest implications is to understand the performance hit which is incurred due to data moving from stack to heap and vice versa.
Consider the below code snippet. When we move a value type to reference type, data is moved from the stack to the heap. When we move a reference type to a value type, the data is moved from the heap to the stack.
This movement of data from the heap to stack and vice-versa creates a performance hit.
When the data moves from value types to reference types, it is termed ‘Boxing’ and the reverse is termed ‘UnBoxing’.

If you compile the above code and see the same in ILDASM, you can see in the IL code how ‘boxing’ and ‘unboxing’ looks. The figure below demonstrates the same:

Performance Implication of Boxing and Unboxing
In order to see how the performance is impacted, we ran the below two functions 10,000 times. One function has boxing and the other function is simple. We used a stop watch object to monitor the time taken.
The boxing function was executed in 3542 ms while without boxing, the code was executed in 2477 ms. In other words, try to avoid boxing and unboxing. In a project where you need boxing and unboxing, use it when it’s absolutely necessary.
With this article, sample code is attached which demonstrates this performance implication.

Currently, I have not included the source code for unboxing but the same holds true for it. You can write code and experiment it using the stopwatch
class.