Diagnosing and Resolving Unexplained High Memory Usage in Windows Server 2008 R2 with SQL Server and IIS


4 views

When your Windows Server 2008 R2 box shows 15.7GB memory usage with no single process accounting for more than 300MB in Task Manager, you're facing one of the most frustrating memory leaks to troubleshoot. This typically happens in environments running SQL Server and IIS together.

First, let's go beyond Task Manager with these PowerShell commands:


# Get total committed memory
Get-Counter '\Memory\Committed Bytes' | Select-Object -ExpandProperty CounterSamples | Select-Object InstanceName, CookedValue

# Check for memory leaks in .NET applications
[System.GC]::GetTotalMemory($true)

# List top memory-consuming processes
Get-Process | Sort-Object WS -Descending | Select-Object -First 10 ProcessName, WS, VM

SQL Server's dynamic memory allocation can be misleading. Run this T-SQL to check:


SELECT 
    physical_memory_in_use_kb/1024 AS [SQL Server Memory Usage (MB)],
    locked_page_allocations_kb/1024 AS [Locked Pages Allocation (MB)],
    total_virtual_address_space_kb/1024 AS [Total VAS (MB)]
FROM sys.dm_os_process_memory;

For IIS worker processes, we need to examine both managed and unmanaged memory:


# Check app pool memory usage
Import-Module WebAdministration
Get-ChildItem IIS:\AppPools | ForEach-Object {
    $mem = (Get-Process -Id $_.workerProcesses.processId -ErrorAction SilentlyContinue).WS / 1MB
    [PSCustomObject]@{
        AppPool = $_.Name
        MemoryMB = [math]::Round($mem, 2)
    }
}

# Dump .NET heap info for w3wp processes
Get-Process w3wp | ForEach-Object {
    & "$env:windir\Microsoft.NET\Framework64\v4.0.30319\adplus.exe" -hang -pn $_.Name -o "C:\Dumps\$($_.Name)_$((Get-Date).ToString('yyyyMMddHHmmss'))"
}

Use PoolMon to check for kernel memory leaks:


# Download from Windows SDK and run:
poolmon.exe /g /p

Based on multiple troubleshooting scenarios, here's an effective workflow:

  1. Set SQL Server max memory to 60-70% of total RAM
  2. Configure IIS application pool recycling (both time-based and memory-based)
  3. Enable IIS failed request tracing for memory-intensive pages
  4. Consider disabling dynamic compression if memory pressure is high

-- SQL Server memory configuration
EXEC sp_configure 'show advanced options', 1;
RECONFIGURE;
EXEC sp_configure 'max server memory', 10240; -- 10GB for 16GB system
RECONFIGURE;

Implement this PowerShell monitoring script to run as a scheduled task:


$logFile = "C:\MemoryLogs\memory_$(Get-Date -Format 'yyyyMMdd').csv"
$data = @{
    Timestamp = Get-Date
    TotalMemory = (Get-CimInstance Win32_ComputerSystem).TotalPhysicalMemory/1GB
    AvailableMemory = (Get-Counter '\Memory\Available MBytes').CounterSamples.CookedValue/1024
    SQLMemory = (Invoke-Sqlcmd -Query "SELECT physical_memory_in_use_kb/1024/1024 AS MemoryGB FROM sys.dm_os_process_memory").MemoryGB
    IISMemory = (Get-Process w3wp | Measure-Object WS -Sum).Sum/1GB
}

$data | Export-Csv $logFile -Append -NoTypeInformation

When your Windows Server 2008 R2 box reports 15.7GB/16GB memory usage but no single process exceeds 300MB in Task Manager, you're facing one of the most frustrating scenarios for sysadmins. Let's dissect this systematically.

First, understand that Task Manager shows working set memory, not total committed memory. For deeper analysis, use these PowerShell commands:

# Get total committed memory
Get-Counter '\Memory\Committed Bytes'

# List all processes with private working set
Get-Process | Sort-Object -Property WS -Descending | Select-Object -First 20

# Check for memory-mapped files
rammap.exe - from Sysinternals Suite

SQL Server uses memory in non-obvious ways. Run this T-SQL query to check buffer pool usage:

SELECT 
    (physical_memory_in_use_kb/1024) AS [SQL Server Memory Usage (MB)],
    (locked_page_allocations_kb/1024) AS [Locked Pages Allocation (MB)],
    (total_virtual_address_space_kb/1024) AS [Total VA Space (MB)],
    (virtual_address_space_committed_kb/1024) AS [Committed VA Space (MB)]
FROM sys.dm_os_process_memory;

For IIS, focus on:

  • Application Pool recycling settings
  • Memory-based recycling triggers
  • Garbage collection behavior

Check current AppPool memory usage with:

Import-Module WebAdministration
Get-ChildItem IIS:\AppPools | ForEach-Object {
    $mem = (Get-ItemProperty "IIS:\AppPools\$($_.Name)" -Name recycling.periodicRestart.privateMemory).Value
    [PSCustomObject]@{
        Name = $_.Name
        PrivateMemoryLimit = "$($mem/1024) MB"
    }
}

Driver leaks often manifest as "paged pool" or "nonpaged pool" growth. Use PoolMon from Windows Driver Kit:

poolmon.exe /b /p /t

Look for tags with steadily increasing Allocs count and minimal Frees count.

  1. Establish baseline memory usage after reboot
  2. Monitor growth patterns (hourly snapshots for 72 hours)
  3. Configure Performance Monitor to track:
    • \Process(*)\Private Bytes
    • \Memory\Pool Nonpaged Bytes
    • \Process(sqlservr)\Handle Count

Consider using Windows Performance Recorder to capture detailed memory usage patterns, then analyze in Windows Performance Analyzer. The key is to correlate memory allocation patterns with system events.