When dealing with IIS6 on Windows Server 2003 x86, we're bound by the 32-bit architecture's fundamental limitations:
// Theoretical memory limits:
Total addressable space: 4GB
Kernel mode space: ~2GB (default)
User mode space: ~2GB
Per-process limit: ~1.2-1.4GB (practical limit)
The 500MB limitation you're observing isn't a hard IIS6 cap but rather a combination of factors:
- Application Pool Recycling Settings: Default recycling occurs at 60% of available memory
- .NET GC Behavior: Managed heaps tend to stay below process limits
- Fragmentation: Address space fragmentation in long-running processes
Modify the following in your application pool properties:
// Set in applicationHost.config or IIS Manager
<applicationPools>
<add name="YourAppPool"
memoryLimit="800" // MB
privateMemoryLimit="0" // Disables recycling
/>
</applicationPools>
When dealing with memory-intensive .NET apps:
// Example web.config modification for large object heaps
<configuration>
<runtime>
<gcAllowVeryLargeObjects enabled="true"/>
<gcServer enabled="true"/>
</runtime>
</configuration>
Use Performance Monitor to track these key counters:
Process(w3wp)\Private Bytes
Process(w3wp)\Virtual Bytes
.NET CLR Memory\% Time in GC
Memory\Available MBytes
For deeper analysis, enable user mode dump collection:
// Using Debug Diagnostic Tool
adplus.exe -hang -pn w3wp.exe -o c:\dumps
If you consistently hit memory limits:
- Implement application partitioning across multiple servers
- Consider x64 migration (requires OS upgrade)
- Evaluate memory optimization techniques like object pooling
// Example object pooling implementation
public class ObjectPool<T> where T : new()
{
private readonly ConcurrentBag<T> _items = new();
public T Get() => _items.TryTake(out T item) ? item : new T();
public void Return(T item) => _items.Add(item);
}
On Windows Server 2003 x86 (even with SP2), the fundamental limitation comes from the 32-bit architecture. Despite having 4GB physical RAM, the OS can only address approximately 2.8-3.2GB due to:
- Memory-mapped hardware addresses
- System reserved memory space
- Kernel-mode memory requirements
The 500MB limit you're observing isn't an arbitrary IIS6 restriction - it's the practical outcome of several interacting factors:
// Example of memory threshold detection in .NET
private static void MonitorMemory() {
long maxMemory = Process.GetCurrentProcess().MaxWorkingSet.ToInt64();
Console.WriteLine($"Process memory limit: {maxMemory/1024/1024}MB");
if (Environment.Is64BitProcess) {
throw new PlatformNotSupportedException("32-bit limitation detected");
}
}
Per-process memory fragmentation: The .NET heap becomes fragmented in long-running processes, preventing efficient memory usage even when theoretical space exists.
Thread Stack Allocation: Each thread consumes ~1MB (default stack size), which counts against your process limit.
// Example showing thread stack impact
public class ThreadTest {
public static void Main() {
for(int i=0; i<500; i++) {
new Thread(() => Thread.Sleep(Timeout.Infinite)).Start();
Console.WriteLine($"Threads: {i}, Memory: {Process.GetCurrentProcess().WorkingSet64/1024/1024}MB");
}
}
}
1. Application Pool Recycling Configuration:
cscript.exe adsutil.vbs SET W3SVC/AppPools/YourAppPool/PeriodicRestartMemory 1000000
2. Enable 3GB Switch in boot.ini:
multi(0)disk(0)rdisk(0)partition(1)\WINDOWS="Windows Server 2003" /3GB /USERVA=3030
3. Implement Memory-Optimized Coding:
// Bad practice - holds large dataset in memory
public List<Customer> GetAllCustomers() {
return _context.Customers.ToList();
}
// Better - streams data
public IEnumerable<Customer> StreamCustomers() {
return _context.Customers.AsNoTracking();
}
Common cases where you get OOM exceptions despite available memory:
- GDI object leaks (each handle consumes USER space)
- Too many assemblies loaded in AppDomain
- Memory-mapped file limitations
// Diagnose GDI handles
[DllImport("user32.dll")]
public static extern int GetGuiResources(IntPtr hProcess, int uiFlags);
var gdiCount = GetGuiResources(Process.GetCurrentProcess().Handle, 0);
var userCount = GetGuiResources(Process.GetCurrentProcess().Handle, 1);