Partager via


Nettoyage des mémoires et performances

Cet article décrit les problèmes liés au garbage collection et à l’utilisation de la mémoire. Il résout les problèmes liés au tas managé et explique comment réduire l’effet du garbage collection sur vos applications. Chaque problème comporte des liens vers des procédures que vous pouvez utiliser pour examiner les problèmes.

Outils d’analyse des performances

Les sections suivantes décrivent les outils disponibles pour examiner les problèmes d’utilisation de la mémoire et de garbage collection. Les procédures fournies plus loin dans cet article font référence à ces outils.

Compteurs de performances de mémoire

Vous pouvez utiliser des compteurs de performances pour collecter des données de performances. Pour obtenir des instructions, consultez Profilage du runtime. La catégorie mémoire CLR .NET des compteurs de performances, comme décrit dans Les compteurs de performances dans .NET, fournit des informations sur le garbage collector.

Débogage avec SOS

Vous pouvez utiliser le débogueur Windows (WinDbg) pour inspecter des objets sur le tas managé.

Pour installer WinDbg, installez les outils de débogage pour Windows à partir de la page Télécharger les outils de débogage pour Windows .

Événements ETW de garbage collection

Le suivi d’événements pour Windows (ETW) est un système de suivi qui complète la prise en charge du profilage et du débogage fournie par .NET. À compter de .NET Framework 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 , déclenché lorsqu’un garbage collection est sur le point de se produire, fournit les informations suivantes :

  • Quelle génération d’objets 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 associés au garbage collection. Un processus peut fournir ses propres événements conjointement avec les événements ETW. Lors de la journalisation, les événements de l’application et les événements de garbage collection peuvent être corrélés pour déterminer comment et quand des problèmes de tas se produisent. Par exemple, une application serveur peut fournir des événements au début et à la fin d’une demande cliente.

L’API de profilage

Les interfaces de profilage CLR (Common Language Runtime) fournissent des informations détaillées sur les objets affectés pendant le garbage collection. Un profileur peut être averti lorsqu’un garbage collection démarre et se termine. Il peut fournir des rapports sur les objets sur le tas managé, y compris une identification d’objets dans chaque génération. Pour plus d’informations, consultez Vue d’ensemble du profilage.

Les profileurs peuvent fournir des informations complètes. Toutefois, les profils complexes peuvent éventuellement modifier le comportement d’une application.

Surveillance des ressources du domaine d’application

À compter de .NET Framework 4, la supervision des ressources de domaine d’application (ARM) permet aux hôtes de surveiller l’utilisation du processeur et de la mémoire par domaine d’application. Pour plus d’informations, consultez Surveillance des ressources du domaine d’application.

Résoudre les problèmes de performances

La première étape consiste à déterminer si le problème est réellement garbage collection. Si vous déterminez qu’il s’agit, sélectionnez-le dans la liste suivante pour résoudre le problème.

Problème : une exception hors mémoire est levée

Il existe deux cas légitimes pour qu’une personne gérée OutOfMemoryException soit levée :

  • Manque de mémoire virtuelle.

    Le garbage collector alloue de la mémoire du système dans des segments d’une taille prédéfinie. Si une allocation nécessite un segment supplémentaire, mais qu’il n’existe aucun bloc libre contigu gauche dans l’espace mémoire virtuelle du processus, l’allocation pour le tas managé échoue.

  • Pas suffisamment de mémoire physique pour allouer.

Contrôle des performances
Déterminez si l’exception hors mémoire est gérée.
Déterminez la quantité de mémoire virtuelle pouvant être réservée.
Déterminez s’il y a suffisamment de mémoire physique.

Si vous déterminez que l’exception n’est pas légitime, contactez le service client microsoft et le support technique avec les informations suivantes :

  • Pile avec l’exception de mémoire insuffisante managée.
  • Vidage de mémoire complète.
  • Les données qui prouvent qu’il n’est pas une exception de mémoire insuffisante légitime, y compris les données qui montrent 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’affichage de l’utilisation de la mémoire sous l’onglet Performances du Gestionnaire des tâches Windows peut indiquer quand trop de mémoire est utilisée. Toutefois, cet affichage se rapporte au jeu de travail ; elle ne fournit pas d’informations sur l’utilisation de la mémoire virtuelle.

Si vous déterminez que le problème est dû au tas managé, vous devez mesurer le tas managé au fil du temps pour déterminer les modèles.

Si vous déterminez que le problème n’est pas dû au tas managé, vous devez utiliser le débogage natif.

Contrôle des performances
Déterminez la quantité de mémoire virtuelle pouvant être réservée.
Déterminez la quantité de mémoire que le tas managé valide.
Déterminez la quantité de mémoire que le tas managé réserve.
Déterminez les objets volumineux de 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

Lorsqu’il apparaît comme si 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 pouvez également rencontrer ce problème s’il n’y a pas eu de garbage collection pour la génération qui contient un objet mort, ce qui indique que le finaliseur de l’objet mort n’a pas été exécuté. Par exemple, cela est possible lorsque vous exécutez une application STA (Single-Threaded Apartment) et que le thread qui services la file d’attente du finaliseur ne peut pas l’appeler.

Contrôle des 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 d’être finalisés.

Problème : le tas managé est trop fragmenté

Le niveau de fragmentation est calculé en tant que ratio d’espace libre sur la mémoire allouée totale pour la génération. Pour la génération 2, un niveau acceptable de fragmentation n’est pas supérieur à 20%. Étant donné que la génération 2 peut être très importante, le ratio de fragmentation est plus important que la valeur absolue.

Avoir beaucoup d’espace libre dans la génération 0 n’est pas un problème, car il s’agit de la génération où de nouveaux objets sont alloués.

La fragmentation se produit toujours dans le tas d’objets volumineux, car elle n’est pas compactée. Les objets libres adjacents sont naturellement réduits en un seul espace pour répondre aux demandes d’allocation d’objets volumineuses.

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 de l’objet d’une application peut nécessiter une modification et vous devez envisager de réévaluer la durée de vie des objets à long terme.

L’épinglage excessif d’objets peut augmenter la fragmentation. Si la fragmentation est élevée, trop d’objets peuvent avoir été épinglés.

Si la fragmentation de la mémoire virtuelle empêche le garbage collector d’ajouter des segments, les causes peuvent être l’une des suivantes :

  • Chargement et déchargement fréquents de nombreux petits assemblys.

  • Conservation d’un trop grand nombre de références aux objets COM lors de l’interopérabilité avec du code non managé.

  • La création d’objets temporaires volumineux, ce qui entraîne l’allocation et la libération des segments de tas d’objets volumineux.

    Lors de l’hébergement du 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, utilisez l’indicateur STARTUP_HOARD_GC_VM dans l’énumération STARTUP_FLAGS.

Contrôle des 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’existe aucune cause légitime pour la fragmentation, contactez le service clientèle et le support technique Microsoft.

Problème : les pauses de garbage collection sont trop longues

Le garbage collection fonctionne en temps réel, de sorte qu’une application doit être en mesure de tolérer certaines pauses. Un critère pour le temps réel doux est que 95% des opérations doivent se terminer à temps.

Dans le garbage collection simultané, les threads managés sont autorisés à s’exécuter pendant un regroupement, ce qui signifie que les pauses sont très minimes.

Les garbage collections éphémères (générations 0 et 1) ne durent que quelques millisecondes, de sorte que la diminution des pauses n’est généralement pas réalisable. Toutefois, vous pouvez réduire les pauses dans les collections de génération 2 en modifiant le modèle de demandes d’allocation par une application.

Une autre méthode plus précise consiste à utiliser des événements ETW de garbage collection. Vous pouvez trouver les minutages des regroupements en ajoutant les différences d’horodatage pour une séquence d’événements. L’ensemble de la séquence de collecte comprend la suspension du moteur d’exécution, le garbage collection lui-même et la reprise du moteur d’exécution.

Vous pouvez utiliser les notifications de garbage collection pour déterminer si un serveur est sur le point d’avoir un regroupement de génération 2 et si la réacheminement des demandes vers un autre serveur peut faciliter les problèmes liés aux pauses.

Contrôle des 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 importante

La génération 0 est susceptible d’avoir un plus grand nombre d’objets sur un système 64 bits, en particulier lorsque vous utilisez le garbage collection serveur au lieu de garbage collection de stations de travail. Cela est dû au fait que le seuil de déclenchement d’un garbage collection de génération 0 est plus élevé dans ces environnements, et les regroupements de génération 0 peuvent être beaucoup plus volumineux. Les performances sont améliorées lorsqu’une application alloue plus de mémoire avant le déclenchement d’un garbage collection.

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 un temps de traitement important est passé dans un garbage collection, le nombre de regroupements est trop fréquent ou le regroupement dure trop longtemps. Un taux d’allocation accru d’objets sur le tas managé entraîne le garbage collection plus fréquemment. La diminution du taux d’allocation réduit la fréquence des nettoyages de mémoire.

Vous pouvez surveiller les taux d’allocation à l’aide du compteur de Allocated Bytes/second performances. Pour plus d’informations, consultez Compteurs de performances dans .NET.

La durée d’une collection est principalement un facteur du nombre d’objets qui survivent après l’allocation. Le garbage collector doit passer par une grande quantité de mémoire si de nombreux objets restent à collecter. Le travail de compactage des survivants prend beaucoup de temps. Pour déterminer le nombre d’objets gérés pendant une 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ôle des performances
Déterminez si une utilisation élevée du processeur est due au garbage collection.
Définissez un point d’arrêt à la fin du garbage collection.

Instructions de dépannage

Cette section décrit les instructions que vous devez prendre en compte lorsque vous commencez vos enquêtes.

Garbage collection de stations de travail ou serveur

Déterminez si vous utilisez le type correct de garbage collection. Si votre application utilise plusieurs threads et instances d’objet, utilisez le garbage collection de serveurs plutôt que le garbage collection de stations de travail. Le garbage collection de serveur fonctionne sur plusieurs threads, tandis que le garbage collection de stations de travail nécessite plusieurs instances d’une application pour exécuter leurs propres threads de garbage collection et concurrencer le temps processeur.

Une application qui a une charge faible et qui effectue des tâches rarement en arrière-plan, comme un service, peut utiliser le garbage collection de stations 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 de mesure cohérent pour diagnostiquer efficacement les problèmes de performances. Tenez compte des points suivants pour établir une planification :

  • Si vous mesurez après un garbage collection de génération 2, l’intégralité du tas managé sera libre de garbage (objets morts).
  • Si vous mesurez immédiatement après un garbage collection de génération 0, les objets des générations 1 et 2 ne seront pas encore collectés.
  • Si vous mesurez immédiatement avant un garbage collection, vous mesurez autant d’allocation que possible avant le démarrage du garbage collection.
  • La mesure pendant un garbage collection est problématique, car les structures de données du garbage collector ne sont pas dans un état valide pour la traversée et peuvent ne pas être en mesure de vous donner les résultats complets. C'est intentionnel.
  • 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, de sorte que la taille du tas peut être identique ou plus grande (la fragmentation peut le rendre plus grande).
  • Le garbage collection simultané de la génération 2 est retardé lorsque la charge de mémoire physique est trop élevée.

La procédure suivante explique comment définir un point d’arrêt afin de pouvoir mesurer le tas managé.

Pour définir un point d’arrêt à la fin du garbage collection

  • Dans WinDbg avec l’extension du débogueur SOS chargée, entrez la commande suivante :

    bp mscorwks!WKS::GCHeap::RestartEE "j (dwo(mscorwks!WKS::GCHeap::GcCondemnedGeneration)==2) 'kb';'g'"

    Définissez GcCondemnedGeneration la génération souhaitée. Cette commande nécessite des symboles privés.

    Cette commande force un arrêt s’il RestartEE est exécuté après la récupération des objets de génération 2 pour le garbage collection.

    Dans le garbage collection de serveurs, un seul appel de thread RestartEEne se produit donc qu’une seule fois lors d’un garbage collection de génération 2.

Procédures de vérification des performances

Cette section décrit les procédures suivantes pour isoler la cause de votre problème de performances :

Pour déterminer si le problème est dû au garbage collection

  • Examinez les deux compteurs de performances de mémoire suivants :

    • % heure dans GC. Affiche le pourcentage de temps écoulé passé à effectuer un garbage collection après le dernier cycle de garbage collection. Utilisez ce compteur pour déterminer si le garbage collector passe trop de temps pour rendre l’espace de tas managé disponible. Si le temps passé dans le garbage collection est relativement faible, cela peut indiquer un problème de ressource 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 validé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 utilisée par votre application.

    La plupart des compteurs de performances de mémoire sont mis à jour à la fin de chaque garbage collection. Par conséquent, ils peuvent ne pas refléter les conditions actuelles sur lesquelles vous souhaitez obtenir des informations.

Pour déterminer si l’exception hors mémoire est gérée

  1. Dans le débogueur WinDbg ou Visual Studio avec l’extension de débogueur SOS chargée, entrez la commande d’exception d’impression (pe) :

    !pe

    Si l’exception est gérée, OutOfMemoryException s’affiche en tant que type d’exception, comme illustré dans l’exemple suivant.

    Exception object: 39594518
    Exception type: System.OutOfMemoryException
    Message: <none>
    InnerException: <none>
    StackTrace (generated):
    
  2. Si la sortie ne spécifie pas d’exception, vous devez déterminer le thread à partir duquel l’exception hors mémoire provient. Entrez 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é.

    28adfb44 7923918f 5b61f2b4 00000000 5b61f2b4 mscorwks!RaiseTheException+0xa0
    
  3. Vous pouvez utiliser la commande suivante pour vider les exceptions imbriquées.

    !pe -nested

    Si vous ne trouvez aucune exception, l’exception hors mémoire provient du code non managé.

Pour déterminer la quantité de mémoire virtuelle pouvant être réservée

  • Dans WinDbg avec l’extension de débogueur SOS chargée, entrez la commande suivante pour obtenir la plus grande région gratuite :

    !address -summary

    La plus grande région 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 région libre est d’environ 24 000 Ko (3A980 en hexadécimal). Cette région est beaucoup plus petite que ce dont le garbage collector a besoin pour un segment.

    -ou-

  • Utilisez la commande vmstat :

    !vmstat

    La plus grande région libre est la plus grande valeur de la colonne MAXIMUM, comme indiqué dans 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 s’il y a suffisamment de mémoire physique

  1. Démarrez le Gestionnaire des tâches Windows.

  2. Sous l’onglet Performance , examinez la valeur validée. (Dans Windows 7, regardez Commit (KB) dans le System groupfichier .)

    Si l’élément Total est proche de celui-ci, vous êtes en cours d’exécution faible sur la Limitmémoire physique.

Pour déterminer la quantité de mémoire que le tas managé valide

  • Utilisez le # Total committed bytes compteur de performances de mémoire pour obtenir le nombre d’octets validés par le tas managé. Le garbage collector valide les blocs sur un segment en fonction des besoins, pas tous en même temps.

    Remarque

    N’utilisez pas le # Bytes in all Heaps compteur de performances, 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 est en fait sa taille de seuil, c’est-à-dire la taille qui induise un garbage collection si la génération est remplie d’objets. Par conséquent, cette valeur est généralement égale à zéro.

Pour déterminer la quantité de mémoire que le tas managé réserve

  • Utilisez le compteur de performances de # Total reserved bytes mémoire.

    Le garbage collector réserve de la mémoire dans les segments, et vous pouvez déterminer où un segment commence à l’aide de la eeheap commande.

    Important

    Bien que vous puissiez déterminer la quantité de mémoire allouée par le garbage collector pour chaque segment, la taille du segment est spécifique à l’implémentation et peut être modifiée à tout moment, y compris dans les mises à jour périodiques. Votre application ne doit jamais faire d'hypothèses concernant une taille de segment particulière, ni dépendre de celle-ci. Elle ne doit pas non plus tenter de configurer la quantité de mémoire disponible pour les allocations de segments.

  • Dans le débogueur WinDbg ou Visual Studio avec l’extension de débogueur SOS chargée, entrez 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épart des segments.

Pour déterminer les objets volumineux de la génération 2

  • Dans le débogueur WinDbg ou Visual Studio avec l’extension de débogueur SOS chargée, entrez la commande suivante :

    !dumpheap –stat

    Si le tas managé est volumineux, dumpheap peut prendre un certain temps pour terminer.

    Vous pouvez commencer à analyser à partir des dernières lignes de la sortie, car ils 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 répertorié est une chaîne et 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, entrez les éléments suivants :

    !dumpheap -type System.String -min 150 -max 200

    Voici un exemple de 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 de faire un interne de chaîne. Pour plus d’informations sur l’interne de chaînes, consultez la rubrique de référence pour la String.Intern méthode.

Pour déterminer les références aux objets

  • Dans WinDbg avec l’extension de débogueur SOS chargée, entrez la commande suivante pour répertorier les références aux objets :

    !gcroot

    -ou-

  • Pour déterminer les références d’un objet spécifique, incluez l’adresse :

    !gcroot 1c37b2ac

    Les racines trouvées sur les 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
    

    La gcroot commande peut prendre beaucoup de temps. Tout objet qui n’est pas récupéré par le garbage collection est un objet actif. Cela signifie qu’une racine est directement ou indirectement conservée sur l’objet. Elle doit donc gcroot retourner les informations de 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 n’a pas récupéré d’objets, car les finaliseurs de ces objets ont été suspendus. La GC.WaitForPendingFinalizers méthode permet aux finaliseurs d’effectuer leurs tâches et de résoudre le problème.

Pour déterminer s’il existe des objets en attente d’être finalisés

  1. Dans le débogueur WinDbg ou Visual Studio avec l’extension de débogueur SOS chargée, entrez la commande suivante :

    !finalizequeue

    Examinez le nombre d’objets prêts à être finalisés. Si le nombre est élevé, vous devez examiner pourquoi ces finaliseurs ne peuvent pas progresser du tout ou ne peuvent pas progresser suffisamment rapidement.

  2. Pour obtenir une sortie de threads, entrez la commande suivante :

    !threads -special

    Cette commande fournit une sortie telle que la suivante.

       OSID     Special thread type
    2    cd0    DbgHelper
    3    c18    Finalizer
    4    df0    GC SuspendEE
    

    Le thread de finaliseur indique quel finaliseur, le cas échéant, est en cours d’exécution. Lorsqu’un thread de finaliseur n’exécute pas de finaliseurs, il attend qu’un événement lui indique de faire son travail. La plupart du temps, vous verrez le thread de finaliseur dans cet état, car il s’exécute à THREAD_HIGHEST_PRIORITY et est censé terminer les finaliseurs en cours d’exécution, 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, entrez la commande suivante :

    !dumpheap -type Free -stat

    Cette commande affiche la taille totale de tous les objets libres sur le tas managé, comme illustré 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 de la génération 0, entrez la commande suivante pour les informations de consommation de mémoire par génération :

    !eeheap -gc

    Cette commande affiche une sortie similaire à ce qui suit. La dernière ligne affiche 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 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 montre que la partie de génération 0 du tas utilise 9 Mo d’espace pour les objets et a 7 Mo libres. Cette analyse montre la mesure dans laquelle la génération 0 contribue à la fragmentation. Cette quantité d’utilisation du tas doit être remise du montant total comme 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, entrez la commande suivante :

    !gchandles

    Les statistiques affichées incluent le nombre de handles épinglés, comme l’illustre l’exemple suivant.

    GC Handle Statistics:
    Strong Handles:      29
    Pinned Handles:      10
    

Pour déterminer la durée d’un garbage collection

  • Examinez le compteur de performances de % Time in GC mémoire.

    La valeur est calculée à l’aide d’un exemple de temps d’intervalle. É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 regroupement n’a eu lieu pendant l’intervalle.

    Le temps de collecte est obtenu en multipliant l’intervalle d’échantillonnage par la valeur de pourcentage.

    Les données suivantes montrent quatre intervalles d’échantillonnage de deux secondes, pour une étude de 8 secondes. Les Gen0colonnes et Gen2 les colonnes Gen1indiquent le nombre total de garbage collections terminées à la fin de l’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 ne s’affichent pas lorsque le garbage collection s’est produit, mais vous pouvez déterminer le nombre de garbage collections qui se sont produits dans un intervalle de temps. En supposant le pire cas, le dixième garbage collection de génération 0 a terminé au début de la deuxième intervalle, et le onzee génération 0 garbage collection a terminé à la fin du troisième intervalle. La durée entre la fin du dixième et la fin du 1ième garbage collection est d’environ 2 secondes, et le compteur de performances affiche 3%, de sorte que la durée du 1ième garbage collection de génération 0 était (2 secondes * 3% = 60 ms).

    Dans l’exemple suivant, il existe cinq intervalles.

    Interval    Gen0    Gen1    Gen2     % Time in GC
            1       9       3       1                3
            2      10       3       1                1
            3      11       4       1                1
            4      11       4       1                1
            5      11       4       2               20
    

    Le deuxième garbage collection de génération 2 a commencé pendant le quatrième intervalle et s’est terminé à la cinquième intervalle. En supposant le pire cas, le dernier garbage collection était pour un regroupement de génération 0 qui s’est terminé au début de la troisième période, et le garbage collection de génération 2 a terminé à la fin de la cinquième intervalle. Par conséquent, le temps entre la fin du garbage collection de génération 0 et la fin du garbage collection de génération 2 est de 4 secondes. Étant donné que le % Time in GC compteur est de 20%, la durée maximale pendant laquelle le garbage collection de génération 2 aurait pu être effectué est (4 secondes * 20% = 800 ms).

  • Vous pouvez également déterminer la longueur d’un garbage collection à l’aide d’é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 montrent 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 26us ( –GCSuspendEEEndGCSuspendEEBegin_V1).

    Le garbage collection réel a pris 4,8 ms ( –GCEnd_V1GCStart_V1).

    La reprise des threads managés a pris 21us ( –GCRestartEEEndGCRestartEEBegin).

    La sortie suivante fournit un exemple de garbage collection d’arrière-plan et inclut les champs processus, thread et é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, car le dernier champ est 1. Cela devient garbage collection No. 102019.

    L’événement GCStart se produit parce qu’il est nécessaire de disposer d’un garbage collection éphémère avant de démarrer un garbage collection d’arrière-plan. Cela devient garbage collection No. 102020.

    À 42514170, le garbage collection n° 102020 se termine. Les threads managés sont redémarrés à ce stade. Cette opération est terminée sur le thread 4372, ce qui a déclenché ce garbage collection d’arrière-plan.

    Sur le thread 4744, une suspension se produit. Il s’agit du seul moment auquel 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 du garbage collection d’arrière-plan se trouve à 89931423. Cela signifie que le garbage collection d’arrière-plan a duré environ 47secondes ((89931423-42504816)/1000).

    Pendant que les threads managés sont en cours d’exécution, vous pouvez voir n’importe quel nombre de garbage collections éphémères se produisant.

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, entrez la commande suivante pour afficher tous les threads avec leurs piles d’appels :

    ~*Ko

    Cette commande affiche une sortie similaire à ce qui suit.

    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 faible du système d’exploitation, la pile des appels est similaire, sauf que le thread est le thread finaliseur. Le thread finaliseur obtient une notification asynchrone à faible mémoire et induise le garbage collection.

    Si le garbage collection a été provoqué par l’allocation de mémoire, la pile s’affiche 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 assistance juste-à-temps (JIT_New*) appelle GCHeap::GarbageCollectGenerationfinalement . 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 génération 2 et comment les éviter. Autrement dit, vous souhaitez 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é la collecte de génération 2.

    Par exemple, entrez la commande suivante dans le débogueur pour afficher le début d’une collection de génération 2 :

    !dumpheap –stat

    Exemple de sortie (abrégé pour afficher 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 afficher 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 double[] objets 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 double[] objets ont été la raison pour laquelle ce garbage collection de génération 2 s’est produit. Votre prochaine étape consiste à déterminer pourquoi les double[] objets sont là et pourquoi ils sont morts. Vous pouvez demander au développeur de code d’où proviennent ces objets, ou vous pouvez utiliser la gcroot commande.

Pour déterminer si une utilisation élevée du processeur est due au garbage collection

  • Mettre en corrélation la valeur du % Time in GC compteur de performances de mémoire avec l’heure du processus.

    Si la % Time in GC valeur pointe en même temps que le temps du processus, le garbage collection provoque une utilisation élevée du processeur. Sinon, profilez l’application pour trouver où se produit l’utilisation élevée.

Voir aussi