Skip to content

Real-world memory leak patterns in .NET — static collections, events, and bad DI. A hands-on lab with profiling insights.

Notifications You must be signed in to change notification settings

AlexGreatDev/MemoryLeakLab

Repository files navigation

🧠 .NET Core Memory Leak Lab

This project demonstrates real-world memory leak scenarios in .NET Core.
It is designed to help developers understand, reproduce, and detect memory leaks using tools like dotMemory and Visual Studio Diagnostic Tools.


🚀 Getting Started

Prerequisites

Run the App

dotnet run

Once running, open Swagger UI to explore available endpoints.


🔬 Available Endpoints

Method Endpoint Description
GET /api/leakdemo/products /api/leakdemo/products
POST /api/leakdemo/event-handler-leak Simulates leak via event subscription
GET /api/leakdemo/status Displays current memory usage

🧪 Scenario 1: DbContext Singleton Memory Leak (SQLite)

This scenario demonstrates a memory leak caused by registering DbContext as a Singleton instead of Scoped. The DbContext should be created per request to avoid retaining resources and causing memory leaks.

Code Example:

public class AppDbContext : DbContext
{
    public DbSet<Product> Products { get; set; }

    public AppDbContext(DbContextOptions<AppDbContext> options)
        : base(options) { }
}

public void ConfigureServices(IServiceCollection services)
{
    // Incorrectly registering DbContext as Singleton (This can cause Memory Leak)
    services.AddSingleton<AppDbContext>(provider =>
    {
        var options = new DbContextOptionsBuilder<AppDbContext>()
            .UseSqlite("Data Source=app.db")
            .Options;
        return new AppDbContext(options);
    });

    services.AddControllers();
}

By calling the API that interacts with the DbContext, memory usage will steadily increase as new instances of DbContext are not disposed of correctly.

Impact of Memory Leak: The DbContext instance will persist across the application’s lifetime.

This results in memory consumption being constantly high.

The proper approach is to register DbContext as Scoped to ensure it is disposed of after each request.

services.AddScoped<AppDbContext>();

By doing this, you allow the DbContext to be disposed of automatically, freeing up memory and avoiding leaks.


🧪 Scenario 2: Event Handler Leak

Memory leak caused by not unsubscribing from events, leading to retained references.

publisher.OnEvent += HandleEvent;
// Object can't be garbage collected due to active event subscription

Even if references are removed, the event delegate chain keeps them alive.


📈 Memory Status Endpoint

Use the /status endpoint to monitor memory usage in real-time:

GET /api/leakdemo/status

Sample response:

{
  "TotalMemoryBytes": 5242880,
  "TotalMemoryMB": 5.0,
  "GC_Generation0": 4,
  "GC_Generation1": 2,
  "GC_Generation2": 1
}

🧰 Profiling Tools

Visual Studio Diagnostic Tools

  1. Run the app in Debug mode.
  2. Open Debug > Windows > Show Diagnostic Tools.
  3. Select the Memory Usage tab.
  4. Click Take Snapshot before and after leak calls.
  5. Analyze object growth and garbage collection behavior.

JetBrains dotMemory

  1. Start a profiling session.
  2. Trigger memory leaks using API endpoints.
  3. Take snapshots and compare.
  4. Explore retained paths and memory roots.

📁 REST Client Test File

### DbContext Singleton Leak
GET http://localhost:5024/api/leakdemo/products

### Event Handler Leak
POST http://localhost:5024/api/leakdemo/event-handler-leak?count=1000

### Memory Status
GET http://localhost:5024/api/leakdemo/status

👤 Author

Alexander(Alireza)
Software .NET Engineer | Memory Optimization Enthusiast
🔗 LinkedIn🌐 GitHub


⭐️ Star this repo if you find it helpful!

About

Real-world memory leak patterns in .NET — static collections, events, and bad DI. A hands-on lab with profiling insights.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages