You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C# IP扫描器大网段扫描后GC未释放内存致OutOfMemoryException问题

Fixing OutOfMemoryException for Large Subnet Scans in C# IP Scanner

Hey there, let's work through this OutOfMemoryException issue you're facing with your multi-subnet IP scanner. I've dealt with similar problems when building network tools, so here's what I recommend to get this sorted:

First, Rule Out 32-bit Memory Limits

The 4GB virtual memory cap you mentioned is a dead giveaway that your project might be set to 32-bit (x86) in Visual Studio. Even on 64-bit systems, 32-bit apps can only access ~4GB of memory. Fix this first:

  • Right-click your project in Solution Explorer → Properties → Build → Platform target → Select x64
  • This lets your app leverage the full memory available in your deployment environment.

Stop Loading All IPs Into Memory At Once

The biggest culprit here is likely pre-generating every IP in the subnet and storing them in a single collection (like a List<IPAddress>). For a /16 subnet, that's 65,536 IPs—each IPAddress object adds up fast. Instead, generate IPs on-demand using an iterator:

public static IEnumerable<IPAddress> GenerateIpRange(IPAddress startIp, int subnetMask)
{
    // Convert IP to uint for easier arithmetic
    byte[] startBytes = startIp.GetAddressBytes();
    Array.Reverse(startBytes); // BitConverter expects big-endian for uint
    uint startIpUint = BitConverter.ToUInt32(startBytes, 0);

    // Calculate total number of IPs in the subnet
    uint totalIps = (uint)Math.Pow(2, 32 - subnetMask);
    uint endIpUint = startIpUint + totalIps - 1;

    // Yield one IP at a time instead of storing all in memory
    for (uint currentIpUint = startIpUint; currentIpUint <= endIpUint; currentIpUint++)
    {
        byte[] currentBytes = BitConverter.GetBytes(currentIpUint);
        Array.Reverse(currentBytes); // Convert back to little-endian for IPAddress
        yield return new IPAddress(currentBytes);
    }
}

This way, you only have one IPAddress in memory at a time (per iteration) instead of thousands.

Optimize Object Allocation to Reduce GC Pressure

Every time you create a new object (like Ping, custom scan result classes), the GC has to clean it up later. Here's how to cut down on this:

  • Use value types for small data: If you're storing scan metadata (like IP, status, response time), use a struct instead of a class. Structs are value types and don't add to heap memory pressure.
  • Reuse expensive objects with an Object Pool: Ping objects are costly to create/destroy. Use .NET's built-in ObjectPool to reuse them:
    using Microsoft.Extensions.ObjectPool;
    
    // Initialize the pool once
    private static readonly ObjectPool<Ping> _pingPool = new DefaultObjectPool<Ping>(new DefaultPooledPolicy<Ping>());
    
    // Use the pool in your scan logic
    async Task ScanSingleIp(IPAddress ip)
    {
        var ping = _pingPool.Get();
        try
        {
            var reply = await ping.SendPingAsync(ip, 1000);
            // Process the reply (only store results you actually need!)
        }
        finally
        {
            _pingPool.Return(ping); // Return to pool for reuse
        }
    }
    
  • Avoid storing unnecessary data: Don't keep every scan result in memory—write valid/interesting results to a file or database immediately, then discard them from memory.

Tune Garbage Collection for Server Environments

Since your deployment environment doesn't have memory limits, switch to Server GC which is optimized for multi-threaded, memory-heavy apps like your scanner:

  1. Add an app.config (or appsettings.json for .NET Core+) file to your project
  2. Add this configuration:
    <configuration>
      <runtime>
        <gcServer enabled="true"/>
      </runtime>
    </configuration>
    

Server GC uses dedicated threads for garbage collection and is more efficient at reclaiming memory in high-throughput scenarios.

Batch Scans & Trigger GC Strategically

Instead of scanning all IPs in parallel at once, split them into batches (e.g., 1000 IPs per batch). After each batch finishes, you can hint to the GC to clean up unused memory (use this sparingly—overcalling GC hurts performance):

// After finishing a batch
GC.Collect(2, GCCollectionMode.Optimized);
GC.WaitForPendingFinalizers();

This gives the GC a chance to reclaim memory before starting the next batch.

Bonus: Control Concurrency

If you're using async/await for scanning, use a SemaphoreSlim to limit the number of concurrent ping requests. Too many concurrent tasks can lead to excessive memory usage from pending operations:

private static readonly SemaphoreSlim _concurrencySemaphore = new SemaphoreSlim(50); // Limit to 50 concurrent pings

async Task ScanSingleIp(IPAddress ip)
{
    await _concurrencySemaphore.WaitAsync();
    try
    {
        // Your ping logic here
    }
    finally
    {
        _concurrencySemaphore.Release();
    }
}

Start with the x64 platform switch first—it's the quickest win. Then work through the memory optimization steps, and you should see your memory usage drop dramatically even for large subnets.

内容的提问来源于stack exchange,提问作者Swannie

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.19 08:50:10