Votre application ASP.NET sous IIS affiche « Not enough memory resources are available to process this command (0x80070008) » sur Windows Server 2019, alors que le serveur a 64 Go de RAM ? Suivez ce guide opérationnel pour trouver la vraie limite atteinte et corriger durablement.
Vue d’ensemble de la question
Sur un serveur Windows Server 2019 doté de 64 Go de RAM, une application IIS/ASP.NET se termine brutalement avec :
System.Runtime.InteropServices.COMException (0x80070008)
Not enough memory resources are available to process this command
Ce message ne signifie pas que la « RAM physique » est totalement consommée. Il indique plutôt que Windows ne peut plus allouer une ressource mémoire particulière : souvent le pool non paginé du noyau, parfois le pool paginé, l’espace d’adressage 32 bits d’un processus, la desktop heap, ou qu’un quota IIS a été atteint. L’objectif est de mesurer la contrainte réelle, d’identifier la ou les causes (fuite de handles, mauvais paramétrage, pilotes/EDR) et d’appliquer un correctif stable.
Réponse & solutions (vue synthétique)
Étape | Action | Pourquoi / ce qu’elle vérifie ou corrige |
---|---|---|
1. Réparer les fichiers système | Exécuter, en invite Administrateur :DISM /Online /Cleanup-Image /ScanHealth DISM /Online /Cleanup-Image /RestoreHealth sfc /scannow puis redémarrer | Écarte les dysfonctionnements liés à des fichiers corrompus (.dll, pilotes, WinSxS) |
2. Mettre à jour | • Appliquer le dernier cumulative update Windows Server 2019 • Mettre à jour .NET / le framework de l’application | De nombreux correctifs mémoire arrivent via Windows Update et les patches .NET |
3. Vérifier le fichier d’échange | Taille minimale ≈ taille RAM, jamais désactivé | Sans pagefile, Windows ne peut pas évacuer certains pools / augmenter le commit limit |
4. Chercher la vraie limite atteinte | Surveiller avec Performance Monitor : • Memory\Available MBytes • Memory\Pool Nonpaged Bytes • Memory\Pool Paged Bytes • Process(w3wp)\Handle Count / Private Bytes | L’erreur survient souvent quand le pool non paginé ou une plage d’adressage 32 bits est saturée, même si la RAM est libre |
5. Contrôler la configuration IIS | • Désactiver « Enable 32‑bit Applications » (préférer 64‑bits) • Adapter/retirer les limites « Private Memory Limit (KB) » et « Virtual Memory Limit (KB) » des Application Pools | Un processus 32 bits est borné (~2 Go utilisables) ; un quota IIS trop bas force un recyclage abrupt |
6. Détecter les fuites de handles / GDI | • handle.exe -u w3wp.exe (Sysinternals)• PerfMon : Process\Handle Count , Process\GDI Objects | Les fuites d’objets noyau épuisent le pool non paginé |
7. Inspecter les dumps si le défaut persiste | procdump -ma w3wp.exe ⇒ analyse WinDbg : !vm , !poolused , !analyze -v | Pour identifier la classe d’objets ou le module responsable |
8. Ajuster la gestion mémoire du noyau (facultatif) | Registre :HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management • PoolUsageMaximum (ex. 70)• PagedPoolSize (Changer uniquement après analyse, puis redémarrer) | Relève légèrement les plafonds du pool — pas un correctif durable en cas de fuite |
9. Bureau à distance / Desktop Heap | Pour des applis à forte création de fenêtres (GDI), ajuster la desktop heap dans :HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\SubSystems\Windows | Son dépassement peut produire également 0x80070008 |
Mode opératoire détaillé
1) Réparer et assainir le socle Windows
- Ouvrez Invite de commandes (Admin) puis exécutez :
DISM /Online /Cleanup-Image /ScanHealth DISM /Online /Cleanup-Image /RestoreHealth sfc /scannow
Redémarrez. Cela répare les composants WinSxS et des DLL susceptibles de provoquer des comportements erratiques dans IIS, HTTP.SYS ou le noyau mémoire. - Installez les mises à jour Windows Server 2019 et .NET/.NET Framework pertinentes (y compris les cumulative updates). Beaucoup de correctifs de gestion mémoire, de GDI et de pools y sont intégrés.
2) Pagefile : garant du commit système
Le « commit limit » (capacité totale d’allocation virtuelle confirmée) ≠ la RAM physique. Il dépend fortement de la présence et la taille du fichier d’échange (pagefile). Sans pagefile, certains pools ne peuvent jamais s’étendre et vous verrez 0x80070008 malgré des gigaoctets libres.
- Recommandation : pagefile activé, taille minimale ≈ taille de la RAM (ex. 64 Go) ou gestion automatique par Windows.
- Vérifiez rapidement :
wmic pagefile list /format:list
ou en PowerShell :Get-CimInstance Win32_PageFileSetting | Format-List *
3) Identifier la vraie ressource épuisée
Créez un jeu de collecteurs de données (PerfMon) sur 10–30 min couvrant les compteurs ci‑dessous. L’objectif : distinguer pool non paginé, pool paginé, commit global, GDI/USER, et limites par processus.
Objet\Compteur | Indicateur d’alerte | Interprétation |
---|---|---|
Memory\Available MBytes | < 1000 MB de façon durable | Pression mémoire globale, mais pas forcément la cause du 0x80070008 |
Memory\Pool Nonpaged Bytes | Monte régulièrement puis plafonne (près de la limite de build) | Saturation du pool non paginé (fuite de handles/objets noyau, pilote, EDR) |
Memory\Pool Paged Bytes | Très élevé et croissant | Pression sur le pool paginé (drivers, composants noyau) |
Process(w3wp)\Private Bytes | Se rapproche du quota IIS ou d’une limite 32 bits | Quota atteint : recyclage ou crash à prévoir |
Process(w3wp)\Handle Count | Monter en escalier sans redescendre | Fuite de handles (Section, File, RegKey, Event, etc.) |
Process(w3wp)\GDI Objects / USER Objects | > 7 500 et continue de croître | Fuite GDI/USER ; risque de desktop heap & 0x80070008 |
.NET CLR Memory\# Bytes in all Heaps (process w3wp/dotnet) | Augmente sans GC significatif | Fuite managée (objets conservés), différente d’une fuite noyau |
Collecte express via PowerShell :
$counters = '\Memory\Available MBytes',
'\Memory\Pool Nonpaged Bytes',
'\Memory\Pool Paged Bytes',
'\Process(w3wp)\Private Bytes',
'\Process(w3wp)\Handle Count'
Get-Counter -Counter $counters -SampleInterval 5 -MaxSamples 60 | Export-Counter -Path C:\Temp\mem.blg -FileFormat blg
4) Vérifier & corriger la configuration IIS
- Forcer 64 bits : dans Application Pools → Advanced Settings, mettez Enable 32-bit Applications à False pour les pools qui exécutent l’application. En ligne de commande :
%windir%\system32\inetsrv\appcmd set apppool /apppool.name:"MonPool" /enable32BitAppOnWin64:false
Pourquoi ? Un w3wp 32 bits se heurte vite à l’espace d’adressage (~2 Go utilisables par défaut), indépendamment de la RAM. - Levée des quotas mémoire si injustifiés :
%windir%\system32\inetsrv\appcmd set apppool "MonPool" /recycling.periodicRestart.privateMemory:0 %windir%\system32\inetsrv\appcmd set apppool "MonPool" /recycling.periodicRestart.memory:0
Des valeurs trop basses (privateMemory
/memory
) déclenchent un recyclage agressif ou un échec d’allocation assimilé à 0x80070008. - ASP.NET Core hébergé sous IIS : le travail réel est dans
dotnet.exe
derrière w3wp (ANCM). Surveillez les deux processus (Process(dotnet)\Private Bytes
,Handle Count
) et appliquez la politique 64 bits côté build et pool. - Recyclage contrôlé : planifiez un recyclage proactif en heures creuses si une fuite lente est suspectée, le temps de corriger. Évitez les recyclages trop fréquents qui masquent le problème.
5) Détecter une fuite de handles / GDI
Utilisez les utilitaires Sysinternals (pas de lien nécessaire si déjà disponibles sur votre bastion d’admin) :
handle.exe -u w3wp.exe
pour lister et classer les handles ouverts par type et par module. Recherchez des dizaines de milliers de Section, File, RegKey ou Token qui montent sans redescendre.- Process Explorer : inspectez Handle Count, GDI Objects, USER Objects. Au‑delà de ~7 500–9 000 GDI, vous êtes proche de la limite par processus (10 000 par défaut) ; dépassement = erreurs d’allocation + crashs.
Indices applicatifs côté .NET (à corriger par code) :
- Objets non‑
IDisposable
non libérés ?Bitmap
,Graphics
,SqlConnection
,HttpWebResponse
,FileStream
, etc. Assurez‑vous qu’ils sontusing
ouDispose()
. - Fuites GDI classiques (génération d’images à la volée) : ne stockez pas des
Bitmap
dans le cache indéfiniment. Préférez des caches à TTL et supprimez explicitement les objets. - Cache managé non borné (
MemoryCache
,IMemoryCache
) : fixez une size limit + politiques d’expiration, faute de quoi le LOH et la pression GC dérivent.
Exemples C# :
// OK : libère immédiatement les ressources natives
using (var bmp = new Bitmap(w, h))
using (var g = Graphics.FromImage(bmp))
{
// ...
} // Dispose() garanti
// OK : réponse HTTP libérée
using var resp = await httpClient.GetAsync(url, HttpCompletionOption.ResponseHeadersRead);
using var stream = await resp.Content.ReadAsStreamAsync();
// OK : cache borné
services.AddMemoryCache(o => o.SizeLimit = 256);
cache.Set(key, value, new MemoryCacheEntryOptions { Size = 1, SlidingExpiration = TimeSpan.FromMinutes(10) });
6) Analyser un dump quand l’erreur survient
Déclenchez un full user dump du processus fautif au premier signal d’exception :
mkdir C:\Dumps
procdump -ma -e 1 -w w3wp.exe C:\Dumps
Puis ouvrez le dump dans WinDbg :
!analyze -v
!vm
!poolused 2
!handle 0 7
!vm
: vue d’ensemble du commit, du pool non paginé/paginé et des limites.!poolused 2
: consommation du pool par tag. Un tag explose ? Ciblez‑le avec PoolMon ou la télémétrie ETW pour trouver le module.!handle
: type de handles retenus (fuite) et pile d’allocation si disponible.
Astuce : si l’erreur est sporadique, installez un dump automatique via le registre (LocalDumps
) sur w3wp/dotnet pour capturer l’instant critique.
7) Ajustements du noyau (avancés, à manier avec parcimonie)
Seulement après diagnostic concluant, et si une correction applicative tarde, vous pouvez desserrer légèrement certains plafonds :
HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management
PoolUsageMaximum = 70 (DWORD)
PagedPoolSize = 0xFFFFFFFF (auto) ou une valeur adaptée
Redémarrage requis. Ces réglages donnent un peu d’air mais ne corrigent pas une fuite active.
8) Desktop Heap / sessions RDP
Des serveurs qui ouvrent de multiples sessions RDP ou créent dynamiquement de nombreuses fenêtres (reporting, génération d’images, automations) peuvent saturer la desktop heap, entraînant 0x80070008 et des erreurs « Cannot create window ».
Paramètre (avancé) :
HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\SubSystems\Windows
... SharedSection=1024,20480,768
Augmenter prudemment le troisième nombre (non‑interactive desktop) après test. Documentez et surveillez GDI/USER par processus.
9) Antimalware/EDR et pilotes
- Excluez de l’analyse en temps réel
C:\Windows\WinSxS
,%TEMP%
de .NET, et les dossiers d’assemblage temporaires. Le verrouillage agressif peut perturber le chargement d’images et accroître la consommation de handles. - Vérifiez les pilotes récemment ajoutés (stockage, réseau, sauvegarde/instantanés). Un pilote consommant le pool non paginé apparaîtra via
!poolused
/ PoolMon.
Plans d’action prêts à l’emploi
Check rapide (15 minutes)
- Confirmez pagefile activé & dimensionné.
- Vérifiez que le pool IIS est 64 bits et que ses quotas mémoire ne sont pas restrictifs.
- Lancez PerfMon et surveillez
Pool Nonpaged Bytes
,Handle Count
,Private Bytes
. - Si
Handle Count
grimpe en continu : capturezhandle.exe -u w3wp.exe
et planifiez un dump.
Stabilisation (même journée)
- Appliquez les mises à jour OS et .NET.
- Recyclage préventif des pools aux heures creuses.
- Ajoutez une alerte PerfMon : si
Pool Nonpaged Bytes
> 80 % de la limite pendant 2 min → déclencher un script de collecte (dump + handle) et redémarrage contrôlé du pool.
Remédiation durable (S+1)
- Corrigez le code : dispose systématique, caches bornés, pas d’images GDI maintenues, pas d’accès fichier/stream laissés ouverts.
- Nettoyez/replacez les pilotes à l’origine d’un tag de pool problématique.
- Normalisez les exclusions EDR et formalisez un Data Collector Set permanent.
Diagnostic différentiel : comment lire vos mesures
Symptômes | Cause la plus probable | Correctif prioritaire |
---|---|---|
Pool Nonpaged Bytes grimpe jusqu’à une valeur plafond, handles en hausse, RAM encore disponible | Fuite d’objets noyau (handles, GDI/USER), pilote ou EDR | Identifier le type de handle avec handle.exe , tag de pool avec !poolused / PoolMon, mettre à jour/retirer le composant fautif |
Private Bytes du processus 32 bits > 1,5–1,8 Go | Espace d’adressage 32 bits saturé | Basculer l’app pool et le build en 64 bits, supprimer les quotas, revoir les caches |
Available MBytes < 500 MB, swapping intense | Pression mémoire globale | Augmenter le pagefile, réduire les charges, identifier le ou les processus gloutons |
GDI/USER > 9 000 objets, erreurs de création de fenêtre | Fuite GDI/desktop heap | Corriger le code créant des fenêtres/bitmaps, ajuster la desktop heap si nécessaire |
Crashs synchrones aux recyclages planifiés | Quotas IIS trop bas, recycle « brutal » | Mettre privateMemory /memory à 0 (illimité) ou à une valeur réaliste, lisser les recyclages |
Bonnes pratiques côté code ASP.NET (prévenir plutôt que guérir)
- Dispose partout : base de données, streams, images, réponses HTTP,
Timer
,CancellationTokenSource
… - Cache borné :
MemoryCache
/IMemoryCache
avec taille max et TTL. Évitez les dictionnaires statiques qui grandissent sans borne. - Pas de GDI sur le chemin chaud : si possible, externalisez la génération d’images vers un service dédié, ou vérifiez strictement la libération des
Graphics
/Bitmap
. - Surveillance applicative : exposez un endpoint interne « health » qui remonte handle count, private bytes, GDI/USER pour alerter avant saturation.
- Server GC (ASP.NET Framework et Core) : activé par défaut sur serveur ; ce réglage optimise le throughput, mais ne corrige pas une fuite noyau. Ne pas confondre OOM managé et 0x80070008 d’origine noyau.
Commandes utiles (mémo)
:: Etat du commit & pools
rammap.exe :: vue avancée (si disponible)
poolmon.exe /n :: tags de pool (activer le "pool tagging" si requis)
:: Perf rapide
typeperf "\Memory\Pool Nonpaged Bytes" -si 5 -sc 60
:: IIS (64 bits + quotas)
%windir%\system32\inetsrv\appcmd list apppool
%windir%\system32\inetsrv\appcmd set apppool /apppool.name:"MonPool" /enable32BitAppOnWin64:false
%windir%\system32\inetsrv\appcmd set apppool "MonPool" /recycling.periodicRestart.privateMemory:0
%windir%\system32\inetsrv\appcmd set apppool "MonPool" /recycling.periodicRestart.memory:0
:: Dumps
procdump -ma -e 1 -w w3wp.exe C:\Dumps
:: Handles
handle.exe -u w3wp.exe > C:\Temp\handles.txt
Environnements virtualisés et conteneurs : points d’attention
- VM avec mémoire dynamique : un ballooning agressif peut réduire le commit limit pratique. Assurez‑vous d’une réserve minimale suffisante et d’un pagefile présent dans la VM, pas seulement sur l’hôte.
- ASP.NET Core en conteneur Windows : vérifiez les job objects/quotas mémoire côté host. Des limites trop serrées se traduisent par des échecs d’allocation analogues à 0x80070008.
Informations complémentaires utiles
- Origine de 0x80070008 : erreur COM quand Windows ne peut plus allouer des ressources noyau ; elle concerne le pool ou l’espace d’adressage, pas la RAM totale.
- Redémarrage : libère temporairement le pool mais masque la cause. Il vaut mieux identifier la fuite (module tiers, pilote, librairie GDI, objets
IDisposable
non libérés, etc.). - Surveillance continue : un Data Collector Set PerfMon ou Windows Admin Center peut déclencher une alerte avant saturation et recycler en douceur.
- Antivirus / EDR : exclure
C:\Windows\WinSxS
et les dossiers temporaires .NET pour éviter des blocages. - 64 bits partout : compiler en AnyCPU (préférer 64 bits) et exécuter le pool IIS en 64 bits pour éviter la fenêtre de 2 Go d’adresse.
FAQ rapide
Q : Pourquoi j’ai 64 Go de RAM libres et pourtant 0x80070008 ?
R : Parce qu’une ressource spécifique (pool non paginé/paginé, handles, desktop heap, espace d’adressage 32 bits) est épuisée. La RAM libre n’aide pas si le noyau refuse un type d’allocation précis.
Q : Comment savoir si c’est le pool non paginé ?
R : Surveillez Memory\Pool Nonpaged Bytes
; s’il ne cesse de croître jusqu’à un plafond puis que l’application échoue, c’est typique. Les dumps (!poolused
) confirmeront le tag responsable.
Q : Relever PagedPoolSize
/PoolUsageMaximum
suffit‑il ?
R : Non. C’est un palliatif. La vraie correction consiste à réparer la fuite (code, module, pilote) ou à supprimer la contrainte (32 bits/quota).
Q : Dois‑je augmenter le « Private Memory Limit (KB) » IIS ?
R : Si la valeur est trop basse, oui ; sinon laissez à 0 (illimité) et surveillez. Un quota trop strict provoque recyclages et erreurs d’allocation.
Conclusion
Le message « Not enough memory resources are available to process this command (0x80070008) » n’est que le symptôme d’une vraie contrainte : pool noyau, handles, GDI, desktop heap, quotas IIS ou espace 32 bits. En appliquant d’abord DISM/SFC et les mises à jour, puis une surveillance ciblée (Nonpaged Bytes, Handle Count, Private Bytes), vous isolez rapidement la cause. Les correctifs durables passent par le tout 64 bits, la suppression des quotas inadaptés, la réparation du code (dispose, caches bornés) et, si nécessaire, l’ajustement mesuré de la configuration noyau. Résultat : stabilité retrouvée, recyclages prédictibles, et fin des crashs mystères malgré des dizaines de gigaoctets de RAM inutilisés.