Understanding Windows Server 2008’s Premature Swap Usage: Memory Management in IIS/.NET Applications


11 views

When monitoring our Windows Server 2008 R2 instance running IIS with a .NET 4 web application, I observed something counterintuitive: the system was actively using swap space despite having 25% physical memory remaining. This behavior differs significantly from Unix-like systems where swap is typically only engaged when physical memory is exhausted.

Windows employs a proactive memory management strategy called Modified Page Writer that differs from Unix's reactive approach. The key components:

// Simplified view of Windows memory management components
MEMORY_MANAGEMENT {
    Working Set Manager: monitors process memory usage
    Modified Page Writer: moves 'cold' pages to disk proactively
    System Cache: manages file system caching
    Paged/Nonpaged pools: kernel memory allocation
}

The Modified Page Writer thread (part of the Memory Manager) continuously scans physical memory for pages that haven't been accessed recently (typically marked in the page table entry's access bit). When found, these pages become candidates for paging out.

.NET applications exhibit particular memory characteristics that interact with Windows' memory management:

  • GC Heap allocations that become inactive may be paged out
  • Large Object Heap (LOH) fragments can trigger early paging
  • CLR's memory commitment differs from actual usage

From your description (1.7GB RAM, 4.5GB swap, w3wp.exe showing 211MB private working set but releasing 1GB+ swap upon recycle), we can deduce:

// PowerShell command to analyze memory usage
Get-Process w3wp | Select-Object  @{
    Name="PrivateMB";Expression={$_.PrivateMemorySize64/1MB}
}, @{
    Name="WorkingSetMB";Expression={$_.WorkingSet64/1MB}
}, @{
    Name="VirtualMB";Expression={$_.VirtualMemorySize64/1MB}
}, @{
    Name="PagedMB";Expression={$_.PagedMemorySize64/1MB}
}

This reveals the discrepancy between private working set (what Task Manager shows) and the actual committed memory (including paged-out portions).

For IIS/.NET applications, consider these registry tweaks:

Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management]
"LargeSystemCache"=dword:00000000  ; For application servers
"ClearPageFileAtShutdown"=dword:00000000
"DisablePagingExecutive"=dword:00000000  ; Set to 1 to keep kernel in RAM

Instead of relying solely on memory-based recycling, implement a hybrid approach:

<applicationPools>
    <add name="MyAppPool" 
        managedRuntimeVersion="v4.0"
        startMode="AlwaysRunning"
        recycling>
        <periodicRestart 
            time="00:00:00" 
            privateMemory="500000"  ; 500MB
            memory="800000">        ; 800MB working set
            <schedule>
                <add value="00:00:00" />
            </schedule>
        </periodicRestart>
        <processModel 
            idleTimeout="00:00:00" 
            maxProcesses="1" 
            shutdownTimeLimit="00:01:30" 
            startupTimeLimit="00:01:30" 
            pingingEnabled="true" />
    </add>
</applicationPools>

For .NET applications, use these tools to identify true leaks versus managed memory growth:

// Windows Performance Monitor counters to track:
- Process\Private Bytes
- .NET CLR Memory\# Bytes in all Heaps
- Memory\Pages/sec
- Process\Page Faults/sec

Combine with DebugDiag or PerfView to capture memory dumps during growth phases.


Many UNIX administrators transitioning to Windows are surprised by its proactive swap usage behavior. Unlike UNIX systems that typically wait until physical RAM is exhausted before paging, Windows employs a more aggressive memory optimization strategy:

// Example PowerShell command to check current memory usage
Get-Counter '\Memory\Available MBytes'
Get-Counter '\Paging File(_Total)\% Usage'

Windows distinguishes between a process's committed memory (total allocated) and its working set (actively used portions). The system constantly evaluates memory pressure and may page out:

// C# example showing how .NET allocates memory
byte[] buffer = new byte[1000000]; // Commits address space
Array.Clear(buffer, 0, buffer.Length); // Touches pages, bringing into working set

Your observations align with Windows' memory management principles:

  • The 1.7GB EC2 instance size creates significant memory pressure
  • Windows pages out less frequently accessed portions of the .NET worker process
  • The difference between private working set (211MB) and committed memory (~1.2GB) explains the swap usage

Use these tools to gain deeper insight:

// PowerShell to monitor w3wp memory
Get-Process w3wp | Select-Object WS, PM, VM, NPM

For your IIS/.NET environment:

  1. Configure application pool recycling based on private bytes instead of physical memory
  2. Implement memory dump analysis when leaks are suspected
  3. Consider using AWS's memory-optimized instances if pressure persists
// Application pool recycling configuration in PowerShell
Import-Module WebAdministration
Set-ItemProperty "IIS:\AppPools\YourAppPool" -Name recycling.periodicRestart.privateMemory -Value 1000000

For deep memory analysis:

// Create memory dump of w3wp process
procdump -ma w3wp.exe

Then analyze with WinDbg:

!dumpheap -stat
!eeheap -gc