← Back to Articles         
Memory Management and Garbage Collection - Part 2

Memory Management and Garbage Collection - Part 2

Published on 12 Oct 2026     7 min read C#
Memory Management & Garbage Collection

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:

  • FileManager implements IDisposable.

  • using automatically calls Dispose().

  • Resources are released even if an exception occurs inside the block.

Key Points:

  • IDisposable defines the Dispose() method.

  • using ensures deterministic cleanup.

  • using is commonly used with FileStream, SqlConnection, and similar resources.

  • Dispose() does not directly remove an object from managed heap memory.

  • Use IAsyncDisposable and await using for 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

GC.Collect()

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 Employee object 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 Employee object.

  • 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.

Span<T>

Memory<T>

ref struct

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 .Span for direct access

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] = 99 modifies 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> and ReadOnlyMemory<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:

  • DefaultObjectPool manages reusable objects.

  • Get() retrieves an available object or creates one.

  • The StringBuilder is 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 StringBuilder and 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

IDisposable

Defines resource cleanup through Dispose()

using

Automatically invokes Dispose()

Memory Leaks

Objects remain reachable unnecessarily

LOH

Stores large objects, generally 85 KB or more

GC.Collect()

Explicitly requests garbage collection

Weak Reference

Does not prevent an object from being collected

Span<T>

Efficient synchronous memory access

Memory<T>

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.

← Back to Articles