C# Memory Management – Advanced Concepts
1. What is the purpose of IDisposable and the using statement?
IDisposable is an interface used to release resources such as file handles, database connections, and network connections.
The using statement automatically calls Dispose() when execution leaves its scope.
Example:
class FileManager : IDisposable
{
public void Dispose()
{
Console.WriteLine("Resources released");
}
}
using (FileManager file = new FileManager())
{
Console.WriteLine("File processing");
}
Output:
File processing
Resources released
Explanation:
-
FileManagerimplementsIDisposable. -
usingautomatically callsDispose(). -
Resources are released even if an exception occurs inside the block.
Key Points:
-
IDisposabledefines theDispose()method. -
usingensures deterministic cleanup. -
usingis commonly used withFileStream,SqlConnection, and similar resources. -
Dispose()does not directly remove an object from managed heap memory. -
Use
IAsyncDisposableandawait usingfor asynchronous cleanup.
2. What causes memory leaks in .NET applications?
A memory leak occurs when an application unintentionally retains objects that are no longer needed.
Although .NET has Garbage Collection, objects that remain reachable cannot be collected.
Common causes:
-
Static collections retaining objects.
-
Event handlers not being unsubscribed.
-
Unbounded caches.
-
Long-lived references to unused objects.
-
Undisposed resources causing resource leaks.
Example:
class Employee
{
public string Name { get; set; }
}
class Program
{
static List<Employee> employees = new();
static void Main()
{
for (int i = 0; i < 10000; i++)
{
employees.Add(new Employee
{
Name = "John"
});
}
}
}
Explanation:
-
The static list holds references to 10,000 objects.
-
GC cannot collect these objects while they remain reachable.
-
If the list keeps growing without removing unused entries, memory usage increases.
Diagram:
Static List
|
+----> Employee 1
+----> Employee 2
+----> Employee 3
+----> Employee 4
|
+----> More objects
All objects remain reachable
|
v
GC cannot reclaim them
Key Points:
-
GC does not prevent all memory leaks.
-
Static variables can unintentionally retain objects.
-
Event subscriptions can cause memory leaks.
-
Use bounded caches and remove unused references.
-
Dispose unmanaged resources properly.
-
Use memory profilers to identify retained objects.
3. What is the Large Object Heap (LOH)?
The Large Object Heap (LOH) is a special area of the managed heap used to store large objects.
Objects of approximately 85,000 bytes or more are generally allocated on the LOH.
Example:
byte[] data = new byte[100000];
Console.WriteLine(GC.GetGeneration(data));
Typical Output:
2
Explanation:
-
A byte array containing 100,000 elements is created.
-
Its size exceeds the LOH threshold.
-
Therefore, it is allocated on the Large Object Heap.
-
LOH objects are collected during Generation 2 collections.
Diagram:
Managed Heap
|
+---- Small Object Heap
| |
| +---- Generation 0
| +---- Generation 1
| +---- Generation 2
|
+---- Large Object Heap
|
+---- Large arrays
+---- Large objects
Key Points:
-
LOH is generally used for objects of 85,000 bytes or more.
-
LOH is collected with Generation 2.
-
LOH is not normally compacted automatically.
-
Frequent large allocations can increase GC overhead.
-
Use
ArrayPool<T>to reuse large arrays when appropriate.
4. What is the difference between GC.Collect() and automatic garbage collection?
Automatic Garbage Collection is controlled by the .NET runtime.
GC.Collect() explicitly requests a garbage collection.
|
Automatic GC |
|
|---|---|
|
Triggered by runtime |
Requested by developer |
|
Runtime determines appropriate timing |
Forces a collection request immediately |
|
Optimized for application behavior |
May negatively affect performance |
|
Recommended |
Generally not recommended |
Example:
class Employee
{
public int Id { get; set; }
}
void Test()
{
Employee emp = new Employee();
emp = null;
GC.Collect();
GC.WaitForPendingFinalizers();
}
Explanation:
-
An
Employeeobject is created. -
The reference is removed.
-
GC.Collect()requests garbage collection. -
GC.WaitForPendingFinalizers()waits for pending finalizers to complete.
The example is for demonstration only. An object's collection depends on whether it is actually unreachable, including runtime optimizations.
Key Points:
-
GC automatically manages memory.
-
GC.Collect()can trigger unnecessary collections. -
Frequent forced collections can reduce performance.
-
Prefer automatic GC in production applications.
-
Manual collection should be considered only in specific, measured scenarios.
5. What are weak references?
A weak reference allows an application to reference an object without preventing Garbage Collection.
A normal reference keeps an object reachable, but a weak reference does not.
Example:
class Employee
{
public string Name { get; set; }
}
Employee emp = new Employee
{
Name = "John"
};
WeakReference<Employee> weakRef = new(emp);
emp = null;
if (weakRef.TryGetTarget(out Employee? employee))
{
Console.WriteLine(employee.Name);
}
else
{
Console.WriteLine("Object collected");
}
Explanation:
-
A weak reference is created for the
Employeeobject. -
The strong reference is removed.
-
GC is allowed to collect the object.
-
TryGetTarget()checks whether the object is still available.
Diagram:
Strong Reference
|
v
Employee Object
|
v
GC keeps object alive
Weak Reference
|
v
Employee Object
|
v
GC may collect object
Key Points:
-
Weak references do not keep objects alive.
-
Use
WeakReference<T>for type-safe weak references. -
Useful for certain caching scenarios.
-
Always use
TryGetTarget()before accessing the object. -
Weak references do not guarantee that an object will remain available.
6. What are Span<T> and Memory<T>?
Both Span<T> and Memory<T> represent contiguous regions of memory without necessarily copying the underlying data.
Span<T> provides fast, synchronous access to memory.
Memory<T> can be stored in objects and used across asynchronous operations.
|
|
|
|---|---|
|
|
Regular struct |
|
Cannot be stored as a normal class field |
Can be stored in class fields |
|
Designed for synchronous memory operations |
Suitable for asynchronous operations |
|
Provides direct memory access |
Use |
Example:
int[] numbers = { 10, 20, 30, 40, 50 };
// Span<T>
Span<int> span = numbers.AsSpan(1, 3);
span[0] = 99;
// Memory<T>
Memory<int> memory = numbers.AsMemory(2, 2);
Console.WriteLine(string.Join(", ", numbers));
Output:
10, 99, 30, 40, 50
Explanation:
-
AsSpan(1, 3)creates a view of three elements starting at index 1. -
span[0] = 99modifies the original array. -
AsMemory(2, 2)creates a memory view without copying the array. -
Both reference the original underlying storage.
Diagram:
Original Array
[10] [20] [30] [40] [50]
| |
+---------+
|
Span<int>
[20] [30] [40]
No new array is created
Key Points:
-
Both can reduce unnecessary memory allocations.
-
Span<T>is useful for high-performance synchronous processing. -
Memory<T>is useful when memory must be retained or passed through asynchronous workflows. -
Changes to a writable span affect the original memory.
-
ReadOnlySpan<T>andReadOnlyMemory<T>provide read-only views.
7. What is object pooling, and when is it useful?
Object Pooling is a technique that reuses previously created objects instead of creating new ones repeatedly.
It can reduce memory allocations and Garbage Collection overhead.
Example:
Using Microsoft.Extensions.ObjectPool:
using Microsoft.Extensions.ObjectPool;
using System.Text;
var pool = new DefaultObjectPool<StringBuilder>(
new StringBuilderPooledObjectPolicy()
);
// Get object from pool
StringBuilder sb = pool.Get();
try
{
sb.Append("Hello");
sb.Append(" World");
Console.WriteLine(sb.ToString());
}
finally
{
// Return object to pool
pool.Return(sb);
}
Output:
Hello World
Explanation:
-
DefaultObjectPoolmanages reusable objects. -
Get()retrieves an available object or creates one. -
The
StringBuilderis used to build a string. -
Return()returns it to the pool for possible reuse. -
The pooling policy resets the builder when it is returned.
Diagram:
Object Pool
|
v
Get Object
|
v
Use Object
|
v
Return Object
|
v
Object Pool
|
v
Reuse Later
Key Points:
-
Reduces repeated object allocations.
-
Useful for expensive-to-create or frequently reused objects.
-
Common examples include
StringBuilderand reusable buffers. -
Use
ArrayPool<T>for array pooling. -
Objects must be reset before reuse.
-
Do not access an object after returning it to the pool.
-
Pooling simple, inexpensive objects may add unnecessary complexity.
Quick Interview Revision
|
Topic |
Important point |
|---|---|
|
|
Defines resource cleanup through |
|
|
Automatically invokes |
|
Memory Leaks |
Objects remain reachable unnecessarily |
|
LOH |
Stores large objects, generally 85 KB or more |
|
|
Explicitly requests garbage collection |
|
Weak Reference |
Does not prevent an object from being collected |
|
|
Efficient synchronous memory access |
|
|
Memory representation suitable for async workflows |
|
Object Pooling |
Reuses objects to reduce allocations |
Interview Tip: For a senior .NET developer, pay particular attention to memory leaks, IDisposable, LOH, Span<T>, and object pooling, especially when explaining performance optimization in ASP.NET Core Web API applications.