Garbage Collection et niveau de performance
Cette rubrique décrit les problèmes liés au garbage collection et à l'utilisation de la mémoire. Elle traite des problèmes qui concernent le tas managé et explique comment réduire l'effet du garbage collection sur vos applications. Chaque problème contient des liens vers les procédures que vous pouvez utiliser pour analyser les problèmes.
Cette rubrique contient les sections suivantes :
Outils d'analyse des performances
Résolution des problèmes de performances
Instructions relatives à la résolution des problèmes
Procédures de contrôle de performances
Outils d'analyse des performances
Les sections suivantes décrivent les outils disponibles pour analyser les problèmes liés à l'utilisation de la mémoire et au garbage collection. Les procédures décrites plus loin dans cette rubrique font référence à ces outils.
Compteurs de performance de la mémoire
Vous pouvez utiliser des compteurs de performance pour recueillir les données de performance. Pour obtenir des instructions, consultez Génération de profils d'exécution. La catégorie Mémoire CLR .NET des compteurs de performance, telle que décrite dans Compteurs de performance pour la mémoire, fournit des informations à propos du garbage collector.
Débogueurs avec SOS
Vous pouvez utiliser WinDbg (page éventuellement en anglais) ou le débogueur de Visual Studio avec le SOS.dll (Extension de débogage SOS) pour inspecter des objets sur le tas managé.
Pour installer WinDbg, installez les outils de débogage à partir du site web de WDK et des outils de développement (éventuellement en anglais). Pour plus d'informations sur l'utilisation de l'extension de débogage SOS, consultez Comment : utiliser SOS.
Événements ETW de garbage collection
Le suivi d'événements pour Windows (ETW) est un système de traçage qui complète la prise en charge du profilage et du débogage fournie par .NET Framework. En commençant par .NET Framework version 4, les événements ETW de garbage collection capturent des informations utiles pour analyser le tas managé d'un point de vue statistique. Par exemple, l'événement GCStart_V1 qui est déclenché lorsqu'un garbage collection est sur le point de se produire, fournit les informations suivantes :
La génération d'objets qui est collectée.
Ce qui a déclenché le garbage collection.
Type de garbage collection (simultané ou non simultané).
La journalisation des événements ETW est efficace et ne masque pas les problèmes de performances liés au garbage collection. Un processus peut fournir ses propres événements conjointement avec les événements ETW. Une fois journalisés, les événements d'application et de garbage collection peuvent être mis en corrélation pour déterminer comment et quand les problèmes de tas se produisent. Par exemple, une application serveur peut fournir des événements au début et à la fin d'une requête du client.
L'API de profilage
Les interfaces de profilage du common language runtime (CLR) fournissent des informations détaillées sur les objets qui ont été affectés au cours du garbage collection. Un profileur peut être informé lorsqu'un garbage collection commence et se termine. Il peut fournir des rapports sur des objets sur le tas managé, y compris une identification des objets dans chaque génération. Pour plus d'informations, consultez Suivi d'objet dans l'API de profilage.
Les profileurs peuvent fournir des informations complètes. Toutefois, des profileurs complexes risquent de modifier le comportement d'une application.
Analyse de ressource de domaine d'application
En commençant par .NET Framework 4, l'analyse de ressource de domaine d'application (ARM) permet aux hôtes de contrôler l'utilisation du processeur et de la mémoire par domaine d'application. Pour plus d'informations, consultez Analyse de ressource de domaine d'application.
Retour au début
Résolution des problèmes de performances
La première étape consiste à déterminer si le problème est réellement lié au garbage collection. Si vous déterminez que c'est le cas, effectuez une sélection dans la liste suivante pour résoudre le problème.
Une exception de mémoire insuffisante est levée
Le processus utilise trop de mémoire
Le garbage collector ne récupère pas les objets assez rapidement
Le tas managé est trop fragmenté
Les pauses de garbage collection sont trop longues
La génération 0 est trop volumineuse
L'utilisation du processeur pendant un garbage collection est trop élevée
Problème : Une exception de mémoire insuffisante est levée
Il existe deux cas légitimes pour qu'une OutOfMemoryException managée soit levée :
Mémoire virtuelle insuffisante.
Le garbage collector alloue la mémoire du système dans des segments d'une taille prédéterminée. Si une allocation requiert un segment supplémentaire, mais qu'il ne reste aucun bloc contigu libre dans l'espace de la mémoire virtuelle du processus, l'allocation du tas managé échoue.
Quantité de mémoire physique à allouer insuffisante.
Contrôles de performances |
---|
Déterminez si l'exception de mémoire insuffisante est managée. Déterminez la quantité de mémoire virtuelle qui peut être réservée. Déterminez si la quantité de mémoire physique est suffisante. |
Si vous déterminez que l'exception n'est pas légitime, contactez le service clientèle de Microsoft avec les informations suivantes :
La pile avec l'exception de mémoire insuffisante managée.
Vidage mémoire complet.
Données qui prouvent qu'il ne s'agit pas d'une exception de mémoire insuffisante légitime, y compris les données qui indiquent que la mémoire virtuelle ou physique n'est pas un problème.
Problème : Le processus utilise trop de mémoire
Une hypothèse courante est que l'utilisation de la mémoire affichée sur l'onglet Performances du Gestionnaire des tâches de Windows peut indiquer lorsqu'une trop grande quantité de mémoire est utilisée. Toutefois, cet affichage concerne le jeu de travail ; il ne fournit pas d'informations sur l'utilisation de la mémoire virtuelle.
Si vous déterminez que le problème est provoqué par le tas managé, vous devez mesurer ce dernier au fil du temps pour déterminer les modèles.
Si vous déterminez que le problème n'est pas causé par le tas managé, vous devez utiliser le débogage natif.
Contrôles de performances |
---|
Déterminez la quantité de mémoire virtuelle qui peut être réservée. Déterminez la quantité de mémoire validée par le tas managé. Déterminez la quantité de mémoire réservée par le tas managé. Déterminez les objets volumineux dans la génération 2. Déterminez les références aux objets. |
Problème : Le garbage collector ne récupère pas les objets assez rapidement
S'il vous semble que les objets ne sont pas récupérés comme prévu pour le garbage collection, vous devez déterminer s'il existe des références fortes à ces objets.
Vous rencontrerez peut-être également ce problème si aucun garbage collection n'a eu lieu pour la génération qui contient un objet mort, ce qui indique que le finaliseur pour l'objet mort n'a pas été exécuté. Par exemple, cela est possible lorsque vous exécutez une application du thread cloisonné (STA, Single-Threaded Apartment) et que le thread qui dessert la file d'attente du finaliseur ne peut pas l'appeler.
Contrôles de performances |
---|
Vérifiez les références aux objets. Déterminez si un finaliseur a été exécuté. Déterminez s'il existe des objets en attente de finalisation. |
Problème : Le tas managé est trop fragmenté
Le niveau de fragmentation est calculé comme le taux d'espace libre sur la mémoire totale allouée pour la génération. Pour la génération 2, un niveau acceptable de fragmentation ne dépasse pas 20 %. Étant donné que la génération 2 peut devenir très volumineuse, le taux de fragmentation est plus important que la valeur absolue.
Disposer d'une grande quantité d'espace libre dans la génération 0 n'est pas un problème, car il s'agit de la génération dans laquelle des nouveaux objets sont alloués.
La fragmentation se produit toujours dans le tas d'objets volumineux parce qu'il n'est pas compacté. Les objets libres qui sont adjacents sont réduits naturellement à un seul espace pour répondre aux demandes d'allocation d'objets volumineux.
La fragmentation peut devenir un problème dans la génération 1 et la génération 2. Si ces générations ont une grande quantité d'espace libre après un garbage collection, l'utilisation d'objet d'une application peut nécessiter des modifications, et vous devez envisager de réévaluer la durée de vie des objets à long terme.
L'épinglage excessif des objets peut augmenter la fragmentation. Si la fragmentation est élevée, trop d'objets peuvent être épinglés.
Si la fragmentation de la mémoire virtuelle empêche le garbage collector d'ajouter des segments, cela peut être dû à l'un des éléments suivants :
Chargement et déchargement fréquents de nombreux petits assemblys.
Stockage de trop de références aux objets COM lors de l'interaction avec du code non managé.
Création d'objets transitoires volumineux, ce qui amène le tas d'objets volumineux à allouer et libérer fréquemment des segments du tas.
Lorsqu'elle héberge le CLR, une application peut demander que le garbage collector conserve ses segments. Cela réduit la fréquence des allocations de segments. Pour ce faire, vous devez utiliser la balise STARTUP_HOARD_GC_VM dans l'STARTUP_FLAGS, énumération.
Contrôles de performances |
---|
Déterminez la quantité d'espace libre dans le tas managé. Déterminez le nombre d'objets épinglés. |
Si vous pensez qu'il n'y a aucune cause légitime pour la fragmentation, contactez le service clientèle de Microsoft.
Problème : Les pauses de garbage collection sont trop longues
Le garbage collection s'effectue en temps réel souple, de sorte qu'une application doit pouvoir tolérer quelques pauses. Un critère pour le temps réel souple est que 95 % des opérations doivent finir dans les temps.
Dans le garbage collection simultané, les threads managés sont autorisés à s'exécuter pendant un garbage collection, ce qui signifie que les pauses sont très minimes.
Étant donné que les garbage collections éphémères (générations 0 et 1) durent seulement quelques millisecondes, il n'est généralement pas possible de réduire les pauses. Toutefois, vous pouvez réduire les pauses dans les garbage collections de génération 2 en modifiant le modèle des demandes d'allocation par une application.
Une autre méthode, plus précise, consiste à utiliser des événements ETW de garbage collection. Vous pouvez obtenir les durées de garbage collection en ajoutant les différences d'horodatage pour une séquence d'événements. La séquence entière de garbage collection inclut la suspension du moteur d'exécution, le garbage collection proprement dit et le redémarrage du moteur d'exécution.
Vous pouvez utiliser Notifications de garbage collection pour déterminer si un serveur est sur le point de subir un garbage collection de génération 2, et si la redirection des requêtes vers un autre serveur peut atténuer les problèmes liés aux pauses.
Contrôles de performances |
---|
Déterminez la durée d'un garbage collection. Déterminez ce qui a provoqué un garbage collection. |
Problème : La génération 0 est trop volumineuse
La génération 0 est susceptible de contenir un plus grand nombre d'objets sur un système 64 bits, en particulier si vous utilisez le garbage collection de serveur au lieu du garbage collection de station de travail. Cela est dû au fait que le seuil pour déclencher un garbage collection de génération 0 est plus haut dans ces environnements, et que les garbage collections 0 peuvent être beaucoup plus importants. Les performances sont améliorées lorsqu'une application alloue plus de mémoire avant qu'un garbage collection soit déclenché.
Problème : L'utilisation du processeur pendant un garbage collection est trop élevée
L'utilisation du processeur est élevée pendant un garbage collection. Si beaucoup de temps de traitement est consacré au garbage collection, celui-ci est trop fréquent ou dure trop longtemps. Un taux d'allocation plus élevé des objets dans le tas managé entraîne des garbage collections plus fréquents. Réduire le taux d'allocation diminue la fréquence de garbage collection.
Vous pouvez surveiller les taux d'allocation à l'aide du compteur de performance Allocated Bytes/second. Pour plus d'informations, consultez Compteurs de performance pour la mémoire.
La durée d'un garbage collection est principalement un facteur du nombre d'objets qui survivent après l'allocation. Le garbage collector doit parcourir une grande quantité de mémoire s'il reste de nombreux objets à collecter. Le travail nécessaire pour compacter les survivants prend du temps. Pour déterminer le nombre d'objets traités pendant un garbage collection, définissez un point d'arrêt dans le débogueur à la fin d'un garbage collection pour une génération spécifiée.
Contrôles de performances |
---|
Déterminez si l'utilisation élevée du processeur est provoquée par le garbage collection. Définissez un point d'arrêt à la fin du garbage collection. |
Retour au début
Instructions relatives à la résolution des problèmes
Cette section décrit les instructions que vous devez prendre en considération lorsque vous commencez vos examens.
Garbage collection de station de travail ou de serveur
Déterminez si vous utilisez le type approprié de garbage collection. Si votre application utilise plusieurs threads et instances d'objets, utilisez le garbage collection de serveur au lieu du garbage collection de station de travail. Le garbage collection de serveur traite plusieurs threads, tandis que le garbage collection de station de travail requiert que plusieurs instances d'une application exécutent leurs propres threads de garbage collection et entrent en concurrence pour le temps processeur.
Une application qui possède une faible charge et effectue des tâches rarement en arrière-plan, par exemple sous la forme d'un service, peut utiliser le garbage collection de station de travail avec le garbage collection simultané désactivé.
Quand mesurer la taille du tas managé
Sauf si vous utilisez un profileur, vous devez établir un modèle cohérent de mesure pour diagnostiquer efficacement les problèmes de performances. Tenez compte des points suivants pour établir un agenda :
Si vous effectuez des mesures après un garbage collection de génération 2, le tas managé entier ne contient pas de déchets (objets morts).
Si vous effectuez des mesures immédiatement après un garbage collection de génération 0, les objets dans les générations 1 et 2 n'auront pas encore été collectés.
Si vous effectuez des mesures juste avant un garbage collection, vous mesurerez autant d'allocation que possible avant le début du garbage collection.
La mesure pendant un garbage collection est problématique, parce que les structures de données du garbage collector ne sont pas dans un état valide pour le parcours et risquent de ne pas pouvoir vous fournir des résultats complets. Ce problème est dû au design.
Lorsque vous utilisez le garbage collection de station de travail avec le garbage collection simultané, les objets récupérés ne sont pas compactés. Par conséquent, la taille du tas peut être identique ou supérieure (la fragmentation peut la faire paraître supérieure).
Le garbage collection simultané dans la génération 2 est différé lorsque la charge de mémoire physique est trop élevée.
La procédure suivante décrit comment définir un point d'arrêt afin que vous puissiez mesurer le tas managé.
Pour définir un point d'arrêt à la fin du garbage collection
Dans WinDbg avec l'extension de débogueur SOS chargée, tapez la commande suivante :
bp mscorwks!WKS::GCHeap::RestartEE "j (dwo(mscorwks!WKS::GCHeap::GcCondemnedGeneration)==2) 'kb';'g'"
où GcCondemnedGeneration est défini à la génération souhaitée. Cette commande requiert des symboles privés.
Cette commande force un saut si RestartEE est exécuté après que les objets de la génération 2 ont été récupérés pour le garbage collection.
Dans le garbage collection de serveur, un seul thread appelle RestartEE, de sorte que le point d'arrêt se produit une seule fois pendant un garbage collection de génération 2.
Retour au début
Procédures de contrôle de performances
Cette section décrit les procédures suivantes pour isoler la cause du problème de performances :
Déterminez si le problème est causé par le garbage collection.
Déterminez si l'exception de mémoire insuffisante est managée.
Déterminez la quantité de mémoire virtuelle qui peut être réservée.
Déterminez si la quantité de mémoire physique est suffisante.
Déterminez la quantité de mémoire validée par le tas managé.
Déterminez la quantité de mémoire réservée par le tas managé.
Déterminez les objets volumineux dans la génération 2.
Déterminez les références aux objets.
Déterminez si un finaliseur a été exécuté.
Déterminez s'il existe des objets en attente de finalisation.
Déterminez la quantité d'espace libre dans le tas managé.
Déterminez le nombre d'objets épinglés.
Déterminez la durée d'un garbage collection.
Déterminez ce qui a déclenché un garbage collection.
Déterminez si l'utilisation élevée du processeur est provoquée par le garbage collection.
Pour déterminer si le problème est causé par le garbage collection.
Examinez les deux compteurs de performance de la mémoire suivants :
% temps dans le GC. Affiche le pourcentage du temps écoulé qui a été consacré à la réalisation d'un garbage collection depuis le dernier cycle de garbage collection. Utilisez ce compteur pour déterminer si le garbage collector passe trop de temps à rendre l'espace de tas managé disponible. Si le temps passé au garbage collection est relativement faible, cela peut indiquer un problème de ressources en dehors du tas managé. Ce compteur peut ne pas être précis lorsque le garbage collection simultané ou en arrière-plan est impliqué.
Nombre total d'octets validés. Affiche la quantité de mémoire virtuelle, actuellement allouée par le garbage collector. Utilisez ce compteur pour déterminer si la mémoire consommée par le garbage collector est une partie excessive de la mémoire que votre application utilise.
La plupart des compteurs de performance de la mémoire sont mis à jour à la fin de chaque garbage collection. Par conséquent, ils ne reflètent peut-être pas les conditions actuelles sur lesquelles vous souhaitez des informations.
Pour déterminer si l'exception de mémoire insuffisante est managée
Dans le débogueur WinDbg ou Visual Studio avec l'extension de débogueur SOS chargée, tapez la commande d'exception d'impression (pe) :
!pe
Si l'exception est managée, OutOfMemoryException est affiché comme type d'exception, comme indiqué dans l'exemple suivant.
Exception object: 39594518 Exception type: System.OutOfMemoryException Message: <none> InnerException: <none> StackTrace (generated):
Si la sortie ne spécifie pas d'exception, vous devez déterminer le thread de l'exception de mémoire insuffisante. Tapez la commande suivante dans le débogueur pour afficher tous les threads avec leurs piles d'appels :
~*kb
Le thread avec la pile qui a des appels d'exception est indiqué par l'argument RaiseTheException. Il s'agit de l'objet d'exception managée.
28adfb44 7923918f 5b61f2b4 00000000 5b61f2b4 mscorwks!RaiseTheException+0xa0
Vous pouvez utiliser la commande suivante pour vider les exceptions imbriquées.
!pe -nested
Si vous ne trouvez aucune exception, l'exception de mémoire insuffisante provient du code non managé.
Pour déterminer la quantité de mémoire virtuelle qui peut être réservée
Dans WinDbg avec l'extension de débogueur SOS chargée, tapez la commande suivante pour obtenir la plus grande zone libre :
!address -summary
La plus grande zone libre est affichée comme indiqué dans la sortie suivante.
Largest free region: Base 54000000 - Size 0003A980
Dans cet exemple, la taille de la plus grande zone libre est d'environ 24 000 Ko (3A980 au format hexadécimal). Cette zone est beaucoup plus petite que ce dont le garbage collector a besoin pour un segment.
ou
Utilisez la commande vmstat :
!vmstat
La plus grande zone libre est la plus grande valeur dans la colonne MAXIMUM, comme le montre la sortie suivante.
TYPE MINIMUM MAXIMUM AVERAGE BLK COUNT TOTAL ~~~~ ~~~~~~~ ~~~~~~~ ~~~~~~~ ~~~~~~~~~~ ~~~~ Free: Small 8K 64K 46K 36 1,671K Medium 80K 864K 349K 3 1,047K Large 1,384K 1,278,848K 151,834K 12 1,822,015K Summary 8K 1,278,848K 35,779K 51 1,824,735K
Pour déterminer si la quantité de mémoire physique est suffisante
Démarrez le Gestionnaire des tâches Windows.
Dans l'onglet Performances, examinez la valeur validée. (Dans Windows 7, examinez Validation (Ko) dans le groupe du système.)
Si le Total est proche de la Limite, vous n'avez plus assez de mémoire physique.
Pour déterminer la quantité de mémoire validée par le tas managé
Utilisez le compteur de performance de la mémoire # Total committed bytes pour obtenir le nombre d'octets que le tas managé valide. Le garbage collector valide des éléments d'un segment lorsque c'est nécessaire, pas tous en même temps.
Remarque N'utilisez pas le compteur de performance # Bytes in all Heaps, car il ne représente pas l'utilisation réelle de la mémoire par le tas managé.La taille d'une génération est incluse dans cette valeur et correspond en fait à sa taille de seuil, c'est-à-dire à la taille qui induit un garbage collection si la génération est remplie avec des objets.Par conséquent, cette valeur est généralement égale à zéro.
Pour déterminer la quantité de mémoire réservée par le tas managé
Utilisez le compteur de performance de la mémoire # Total reserved bytes.
Le garbage collector réserve la mémoire dans des segments et vous pouvez déterminer où démarre un tas à l'aide de la commande eeheap.
Dans le débogueur WinDbg ou Visual Studio avec l'extension de débogueur SOS chargée, tapez la commande suivante :
!eeheap -gc
Le résultat est le suivant.
Number of GC Heaps: 2 ------------------------------ Heap 0 (002db550) generation 0 starts at 0x02abe29c generation 1 starts at 0x02abdd08 generation 2 starts at 0x02ab0038 ephemeral segment allocation context: none segment begin allocated size 02ab0000 02ab0038 02aceff4 0x0001efbc(126908) Large object heap starts at 0x0aab0038 segment begin allocated size 0aab0000 0aab0038 0aab2278 0x00002240(8768) Heap Size 0x211fc(135676) ------------------------------ Heap 1 (002dc958) generation 0 starts at 0x06ab1bd8 generation 1 starts at 0x06ab1bcc generation 2 starts at 0x06ab0038 ephemeral segment allocation context: none segment begin allocated size 06ab0000 06ab0038 06ab3be4 0x00003bac(15276) Large object heap starts at 0x0cab0038 segment begin allocated size 0cab0000 0cab0038 0cab0048 0x00000010(16) Heap Size 0x3bbc(15292) ------------------------------ GC Heap Size 0x24db8(150968)
Les adresses indiquées par « segment » sont les adresses de début des segments.
Pour déterminer les objets volumineux dans la génération 2
Dans le débogueur WinDbg ou Visual Studio avec l'extension de débogueur SOS chargée, tapez la commande suivante :
!dumpheap –stat
Si le tas managé est volumineux, l'exécution de dumpheap peut prendre un certain temps.
Vous pouvez démarrer l'analyse à partir des dernières lignes de la sortie, car elles répertorient les objets qui utilisent le plus d'espace. Par exemple :
2c6108d4 173712 14591808 DevExpress.XtraGrid.Views.Grid.ViewInfo.GridCellInfo 00155f80 533 15216804 Free 7a747c78 791070 15821400 System.Collections.Specialized.ListDictionary+DictionaryNode 7a747bac 700930 19626040 System.Collections.Specialized.ListDictionary 2c64e36c 78644 20762016 DevExpress.XtraEditors.ViewInfo.TextEditViewInfo 79124228 121143 29064120 System.Object[] 035f0ee4 81626 35588936 Toolkit.TlkOrder 00fcae40 6193 44911636 WaveBasedStrategy.Tick_Snap[] 791242ec 40182 90664128 System.Collections.Hashtable+bucket[] 790fa3e0 3154024 137881448 System.String Total 8454945 objects
Le dernier objet de la liste est une chaîne qui occupe le plus d'espace. Vous pouvez examiner votre application pour voir comment vos objets de chaîne peuvent être optimisés. Pour afficher les chaînes comprises entre 150 et 200 octets, tapez ce qui suit :
!dumpheap -type System.String -min 150 -max 200
Voici un exemple des résultats.
Address MT Size Gen 1875d2c0 790fa3e0 152 2 System.String HighlightNullStyle_Blotter_PendingOrder-11_Blotter_PendingOrder-11 …
L'utilisation d'un entier au lieu d'une chaîne pour un ID peut être plus efficace. Si la même chaîne est répétée des milliers de fois, envisagez l'internement de chaîne. Pour plus d'informations sur l'internement de chaîne, consultez la rubrique de référence pour la méthode String.Intern.
Pour déterminer les références aux objets
Dans WinDbg avec l'extension de débogueur SOS chargée, tapez la commande suivante pour répertorier des références aux objets :
!gcroot
-or-
Pour déterminer les références d'un objet spécifié, incluez l'adresse :
!gcroot 1c37b2ac
Les racines trouvées sur des piles peuvent être des faux positifs. Pour plus d'informations, utilisez la commande !help gcroot.
ebx:Root:19011c5c(System.Windows.Forms.Application+ThreadContext)-> 19010b78(DemoApp.FormDemoApp)-> 19011158(System.Windows.Forms.PropertyStore)-> … [omitted] 1c3745ec(System.Data.DataTable)-> 1c3747a8(System.Data.DataColumnCollection)-> 1c3747f8(System.Collections.Hashtable)-> 1c376590(System.Collections.Hashtable+bucket[])-> 1c376c98(System.Data.DataColumn)-> 1c37b270(System.Data.Common.DoubleStorage)-> 1c37b2ac(System.Double[]) Scan Thread 0 OSTHread 99c Scan Thread 6 OSTHread 484
L'exécution de la commande gcroot peut prendre beaucoup de temps. Tout objet qui n'est pas récupéré au cours du garbage collection est un objet vivant. Cela signifie qu'une racine se maintient directement ou indirectement sur l'objet, et que gcroot doit retourner les informations du chemin d'accès à l'objet. Vous devez examiner les graphiques retournés et voir pourquoi ces objets sont toujours référencés.
Pour déterminer si un finaliseur a été exécuté
Exécutez un programme de test qui contient le code suivant :
GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect();
Si le test résout le problème, cela signifie que le garbage collector ne récupérait pas les objets, parce que les finaliseurs pour ces objets avaient été interrompus. La méthode permet aux finaliseurs GC.WaitForPendingFinalizers de terminer leurs tâches et résout le problème.
Pour déterminer s'il existe des objets en attente de finalisation
Dans le débogueur WinDbg ou Visual Studio avec l'extension de débogueur SOS chargée, tapez la commande suivante :
!finalizequeue
Examinez le nombre d'objets qui sont prêts pour la finalisation. Si le nombre est élevé, vous devez examiner pourquoi les finaliseurs ne peuvent pas progresser du tout ou assez rapidement.
Pour obtenir une sortie des threads, tapez la commande suivante :
threads -special
Cette commande fournit une sortie semblable à la suivante.
OSID Special thread type 2 cd0 DbgHelper 3 c18 Finalizer 4 df0 GC SuspendEE
Le thread finaliseur indique quel finaliseur, le cas échéant, est exécuté actuellement. Lorsqu'un thread finaliseur n'exécute pas de finaliseur, il attend qu'un événement lui indique d'effectuer son travail. La plupart du temps, vous verrez le thread finaliseur dans cet état, car il s'exécute à THREAD_HIGHEST_PRIORITY et est supposé finir d'exécuter les finaliseurs, le cas échéant, très rapidement.
Pour déterminer la quantité d'espace libre dans le tas managé
Dans le débogueur WinDbg ou Visual Studio avec l'extension de débogueur SOS chargée, tapez la commande suivante :
!dumpheap -type Free -stat
Cette commande affiche la taille totale de tous les objets libres dans le tas managé, comme indiqué dans l'exemple suivant.
total 230 objects Statistics: MT Count TotalSize Class Name 00152b18 230 40958584 Free Total 230 objects
Pour déterminer l'espace libre dans la génération 0, tapez la commande suivante pour les informations de consommation de mémoire par génération :
!eeheap -gc
Cette commande affiche une sortie similaire à la suivante. La dernière ligne indique le segment éphémère.
Heap 0 (0015ad08) generation 0 starts at 0x49521f8c generation 1 starts at 0x494d7f64 generation 2 starts at 0x007f0038 ephemeral segment allocation context: none segment begin allocated size 00178250 7a80d84c 7a82f1cc 0x00021980(137600) 00161918 78c50e40 78c7056c 0x0001f72c(128812) 007f0000 007f0038 047eed28 0x03ffecf0(67103984) 3a120000 3a120038 3a3e84f8 0x002c84c0(2917568) 46120000 46120038 49e05d04 0x03ce5ccc(63855820)
Calculez l'espace utilisé par la génération 0 :
? 49e05d04-0x49521f8c
Le résultat est le suivant. La génération 0 est d'environ 9 Mo.
Evaluate expression: 9321848 = 008e3d78
La commande suivante vide l'espace libre dans la plage de la génération 0 :
!dumpheap -type Free -stat 0x49521f8c 49e05d04
Le résultat est le suivant.
------------------------------ Heap 0 total 409 objects ------------------------------ Heap 1 total 0 objects ------------------------------ Heap 2 total 0 objects ------------------------------ Heap 3 total 0 objects ------------------------------ total 409 objects Statistics: MT Count TotalSize Class Name 0015a498 409 7296540 Free Total 409 objects
Cette sortie indique que la partie de la génération 0 du tas utilise 9 Mo de l'espace pour les objets et a 7 Mo libres. Cette analyse indique dans quelle mesure la génération 0 contribue à la fragmentation. Cette quantité d'utilisation du tas doit être retirée de la quantité totale comme étant la cause de fragmentation par les objets à long terme.
Pour déterminer le nombre d'objets épinglés
Dans le débogueur WinDbg ou Visual Studio avec l'extension de débogueur SOS chargée, tapez la commande suivante :
!gchandles
Les statistiques affichées incluent le nombre de handles épinglés, comme l'exemple suivant l'indique.
GC Handle Statistics: Strong Handles: 29 Pinned Handles: 10
Pour déterminer la durée d'un garbage collection
Examinez le compteur de performance de la mémoire % Time in GC.
La valeur est calculée à l'aide d'un exemple d'intervalle de temps. Étant donné que les compteurs sont mis à jour à la fin de chaque garbage collection, l'exemple actuel aura la même valeur que l'exemple précédent si aucun garbage collection n'a eu lieu pendant l'intervalle de temps.
Le temps de garbage collection est obtenu en multipliant l'exemple d'intervalle de temps par la valeur de pourcentage.
Les données suivantes indiquent quatre intervalles d'échantillonnage de deux secondes, pour une étude de 8 secondes. Les colonnes Gen0, Gen1 et Gen2 indiquent le nombre de garbage collections qui ont eu lieu pendant cet intervalle pour cette génération.
Interval Gen0 Gen1 Gen2 % Time in GC 1 9 3 1 10 2 10 3 1 1 3 11 3 1 3 4 11 3 1 3
Ces informations n'indiquent pas quand le garbage collection a eu lieu, mais vous pouvez déterminer le nombre de garbage collections qui se sont produits dans un intervalle de temps. Dans le pire des cas, le dixième garbage collection de la génération 0 s'est terminé au début du deuxième intervalle et le onzième garbage collection de la génération 0 s'est terminé à la fin du cinquième intervalle. Le temps entre la fin du dixième et la fin du onzième garbage collection est d'environ 2 secondes, et le compteur de performance indique 3 %, donc la durée du onzième garbage collection de la génération 0 était (2 secondes * 3 % = 60 ms).
Dans cet exemple, il y a 5 périodes.
Interval Gen0 Gen1 Gen2 % Time in GC 1 9 3 1 3 2 10 3 1 1 3 11 4 2 1 4 11 4 2 1 5 11 4 2 20
Le deuxième garbage collection de la génération 2 a démarré pendant le troisième intervalle et s'est terminé au cinquième intervalle. Dans le pire des cas, le dernier garbage collection était pour un garbage collection de génération 0 qui s'est terminé au début du deuxième intervalle et le garbage collection de la génération 2 s'est terminé à la fin du cinquième intervalle. Par conséquent, le temps entre la fin du garbage collection de la génération 0 et la fin du garbage collection de la génération 2 est de 4 secondes. Étant donné que le compteur % Time in GC a la valeur 20 %, la durée maximale du garbage collection de la génération 2 est (4 secondes * 20 % = 800 ms).
Vous pouvez également déterminer la longueur d'un garbage collection à l'aide des événements ETW de garbage collection et analyser les informations pour déterminer la durée du garbage collection.
Par exemple, les données suivantes indiquent une séquence d'événements qui s'est produite pendant un garbage collection non simultané.
Timestamp Event name 513052 GCSuspendEEBegin_V1 513078 GCSuspendEEEnd 513090 GCStart_V1 517890 GCEnd_V1 517894 GCHeapStats 517897 GCRestartEEBegin 517918 GCRestartEEEnd
La suspension du thread managé a pris 26 us (GCSuspendEEEnd – GCSuspendEEBegin_V1).
Le garbage collection réel a pris 4,8 ms (GCEnd_V1 – GCStart_V1).
La reprise des threads managés a pris 21 us (GCRestartEEEnd – GCRestartEEBegin).
La sortie suivante fournit un exemple pour le garbage collection d'arrière-plan et inclut le processus, le thread et les champs d'événement. (Toutes les données ne sont pas affichées.)
timestamp(us) event name process thread event field 42504385 GCSuspendEEBegin_V1 Test.exe 4372 1 42504648 GCSuspendEEEnd Test.exe 4372 42504816 GCStart_V1 Test.exe 4372 102019 42504907 GCStart_V1 Test.exe 4372 102020 42514170 GCEnd_V1 Test.exe 4372 42514204 GCHeapStats Test.exe 4372 102020 42832052 GCRestartEEBegin Test.exe 4372 42832136 GCRestartEEEnd Test.exe 4372 63685394 GCSuspendEEBegin_V1 Test.exe 4744 6 63686347 GCSuspendEEEnd Test.exe 4744 63784294 GCRestartEEBegin Test.exe 4744 63784407 GCRestartEEEnd Test.exe 4744 89931423 GCEnd_V1 Test.exe 4372 102019 89931464 GCHeapStats Test.exe 4372
L'événement GCStart_V1 à 42504816 indique qu'il s'agit d'un garbage collection d'arrière-plan, parce que le dernier champ est 1. Cela devient le garbage collection No. 102019.
L'événement GCStart se produit parce qu'un garbage collection éphémère est nécessaire avant de démarrer un garbage collection d'arrière-plan. Cela devient le garbage collection No. 102020.
À 42514170, le garbage collection No. 102020 prend fin. Les threads managés sont redémarrés à ce stade. Cette action s'effectue sur le thread 4372, qui a déclenché ce garbage collection d'arrière-plan.
Dans le thread 4744, une suspension se produit. C'est le seul moment où le garbage collection d'arrière-plan doit suspendre les threads managés. Cette durée est d'environ 99 ms 63784407-63685394 () (/1000).
L'événement GCEnd pour le garbage collection d'arrière-plan est à 89931423. Cela signifie que le garbage collection d'arrière-plan a duré environ 47 secondes ((89931423-42504816) (/1000).
Pendant l'exécution des threads managés, vous pouvez voir tous les garbage collections qui sont en cours.
Pour déterminer ce qui a déclenché un garbage collection
Dans le débogueur WinDbg ou Visual Studio avec l'extension de débogueur SOS chargée, tapez la commande suivante pour afficher tous les threads avec leurs piles d'appels :
~*kb
Cette commande affiche une sortie similaire à la suivante.
0012f3b0 79ff0bf8 mscorwks!WKS::GCHeap::GarbageCollect 0012f454 30002894 mscorwks!GCInterface::CollectGeneration+0xa4 0012f490 79fa22bd fragment_ni!request.Main(System.String[])+0x48
Si le garbage collection a été provoqué par une notification de mémoire insuffisante du système d'exploitation, la pile d'appels est similaire, à ceci près que le thread est le thread finaliseur. Le thread finaliseur reçoit une notification asynchrone de mémoire insuffisante et induit le garbage collection.
Si le garbage collection a été provoqué par l'allocation de mémoire, la pile se présente comme suit :
0012f230 7a07c551 mscorwks!WKS::GCHeap::GarbageCollectGeneration 0012f2b8 7a07cba8 mscorwks!WKS::gc_heap::try_allocate_more_space+0x1a1 0012f2d4 7a07cefb mscorwks!WKS::gc_heap::allocate_more_space+0x18 0012f2f4 7a02a51b mscorwks!WKS::GCHeap::Alloc+0x4b 0012f310 7a02ae4c mscorwks!Alloc+0x60 0012f364 7a030e46 mscorwks!FastAllocatePrimitiveArray+0xbd 0012f424 300027f4 mscorwks!JIT_NewArr1+0x148 000af70f 3000299f fragment_ni!request..ctor(Int32, Single)+0x20c 0000002a 79fa22bd fragment_ni!request.Main(System.String[])+0x153
Un programme d'assistance juste-à-temps (JIT_New*) appelle finalement GCHeap::GarbageCollectGeneration. Si vous déterminez que les garbage collections de génération 2 sont provoqués par des allocations, vous devez déterminer quels objets sont collectés par un garbage collection de la génération 2 et la façon de les éviter. Autrement dit, vous devez déterminer la différence entre le début et la fin d'un garbage collection de génération 2 et les objets qui ont provoqué le garbage collection de génération 2.
Par exemple, tapez la commande suivante dans le débogueur pour indiquer le début d'un garbage collection de génération 2 :
!dumpheap –stat
Exemple de sortie (abrégé pour indiquer les objets qui utilisent le plus d'espace) :
79124228 31857 9862328 System.Object[] 035f0384 25668 11601936 Toolkit.TlkPosition 00155f80 21248 12256296 Free 79103b6c 297003 13068132 System.Threading.ReaderWriterLock 7a747ad4 708732 14174640 System.Collections.Specialized.HybridDictionary 7a747c78 786498 15729960 System.Collections.Specialized.ListDictionary+DictionaryNode 7a747bac 700298 19608344 System.Collections.Specialized.ListDictionary 035f0ee4 89192 38887712 Toolkit.TlkOrder 00fcae40 6193 44911636 WaveBasedStrategy.Tick_Snap[] 7912c444 91616 71887080 System.Double[] 791242ec 32451 82462728 System.Collections.Hashtable+bucket[] 790fa3e0 2459154 112128436 System.String Total 6471774 objects
Répétez la commande à la fin de la génération 2 :
!dumpheap –stat
Exemple de sortie (abrégé pour indiquer les objets qui utilisent le plus d'espace) :
79124228 26648 9314256 System.Object[] 035f0384 25668 11601936 Toolkit.TlkPosition 79103b6c 296770 13057880 System.Threading.ReaderWriterLock 7a747ad4 708730 14174600 System.Collections.Specialized.HybridDictionary 7a747c78 786497 15729940 System.Collections.Specialized.ListDictionary+DictionaryNode 7a747bac 700298 19608344 System.Collections.Specialized.ListDictionary 00155f80 13806 34007212 Free 035f0ee4 89187 38885532 Toolkit.TlkOrder 00fcae40 6193 44911636 WaveBasedStrategy.Tick_Snap[] 791242ec 32370 82359768 System.Collections.Hashtable+bucket[] 790fa3e0 2440020 111341808 System.String Total 6417525 objects
Les objets double[] ont disparu de la fin de la sortie, ce qui signifie qu'ils ont été collectés. Ces objets représentent environ 70 Mo. Les objets restants n'ont pas beaucoup changé. Par conséquent, ces objets double[] étaient la raison pour laquelle le garbage collection de génération 2 s'est produit. L'étape suivante consiste à déterminer pourquoi les objets double[] sont présents et pourquoi ils sont morts. Vous pouvez demander d'où viennent ces objets au développeur du code, ou utiliser la commande gcroot.
Pour déterminer si l'utilisation élevée du processeur est provoquée par le garbage collection
Mettez en corrélation la valeur du compteur de performance de la mémoire % Time in GC avec le temps de traitement.
Si la valeur % Time in GC connaît des pics au même moment que le temps de traitement, le garbage collection provoque une utilisation élevée du processeur. Sinon, profilez l'application pour savoir quand l'utilisation élevée se produit.