Garbage collection et performances

Cet article décrit les problèmes liés au nettoyage de la mémoire et à l’utilisation de la mémoire. Elle apporte des solutions aux problèmes concernant les tas managés et explique comment réduire l’effet du garbage collection sur vos applications. Chaque problème contient des liens vers des procédures à suivre pour résoudre le problème.

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 fournies plus loin dans cet article font référence à ces outils.

Compteurs de performance mémoire

Vous pouvez utiliser les compteurs de performances pour collecter des données sur les performances. Pour obtenir des instructions, voir Génération de profils d'exécution. La catégorie Mémoire CLR .NET des compteurs de performances, telle qu’elle est décrite dans Compteurs de performances dans .NET, fournit des informations sur le récupérateur de mémoire.

Débogage avec l'extension SOS

Vous pouvez utiliser le Débogueur Windows (WinDbg) pour inspecter les objets du tas managé.

Pour installer WinDbg, installez les outils de débogage pour Windows à partir de la page 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 nettoyage de la mémoire capturent des informations utiles pour l’analyse du tas managé d’un point de vue statistique. Par exemple, l'événement GCStart_V1, qui est déclenché quand 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
  • Le 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 aux événements ETW. Quand ils sont journalisés, les événements d'application et les événements de garbage collection peuvent être corrélés pour déterminer quand et comment 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 demande 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 pendant le garbage collection. Un profileur peut être informé quand un garbage collection commence et se termine. Il peut fournir des rapports sur les objets du tas managé, y compris une identification des objets au cours de chaque génération. Pour plus d'informations, voir Vue d'ensemble du profilage.

Les profileurs peuvent fournir des informations complètes. Toutefois, les profileurs complexes peuvent potentiellement modifier le comportement d'une application.

Analyse de ressource de domaine d'application

À compter de .NET Framework 4, l’outil ARM (Application Domain Resource Monitoring) permet aux hôtes de superviser l’utilisation du processeur et de la mémoire par domaine d’application. Pour plus d'informations, voir Analyse de ressource de domaine d'application.

Résoudre les problèmes de performances

La première étape consiste à déterminer si le problème est effectivement lié au garbage collection. Si c'est le cas, cliquez sur l'un des liens suivants pour résoudre le problème.

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

Il existe deux cas légitimes pour la levée d'une OutOfMemoryException managée :

  • Une mémoire virtuelle insuffisante.

    Le garbage collector alloue la mémoire depuis le système en segments d'une taille prédéterminée. Si une allocation requiert un segment supplémentaire, mais qu'il n'existe aucun bloc contigu libre dans l'espace de mémoire virtuelle du processus, l'allocation du tas managé échoue.

  • Ne pas avoir suffisamment de mémoire physique à allouer.

Contrôle des performances
Déterminez si l’exception de mémoire insuffisante est gérée.
Identifiez la quantité de mémoire virtuelle pouvant être réservée.
Déterminez si la mémoire physique est suffisante.

Si vous déterminez que l'exception n'est pas légitime, contactez le support technique Microsoft et préparez-vous à fournir les informations suivantes :

  • La pile où a été levée l'exception d'insuffisance de mémoire managée.
  • Le fichier complet de vidage mémoire.
  • Les données qui prouvent qu'il ne s'agit pas d'une exception de mémoire insuffisante légitime, y compris les données indiquant que ni la mémoire virtuelle ni la mémoire physique ne posent un problème.

Problème : le processus utilise trop de mémoire

On suppose souvent que l’utilisation de la mémoire qui s’affiche sous l’onglet Performances du Gestionnaire des tâches Windows peut indiquer à l’utilisateur que trop de mémoire est utilisée. Toutefois, cet affichage concerne le jeu de travail et 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 examiner le tas managé dans le temps pour déterminer les éventuels schémas récurrents.

Si vous déterminez que le problème n'est pas provoqué par le tas managé, vous devez utiliser le débogage natif.

Contrôle des performances
Identifiez la quantité de mémoire virtuelle pouvant être réservée.
Identifiez la quantité de mémoire validée par le tas managé.
Identifiez la quantité de mémoire réservée par le tas managé.
Identifiez les objets volumineux dans la génération 2.
Identifiez les références aux objets.

Problème : le garbage collector ne récupère pas les objets suffisamment rapidement

Quand les objets semblent ne pas être récupérés comme prévu pour le garbage collection, vous devez déterminer s'il existe des références solides à ces objets.

Vous pouvez également rencontrer 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 de l'objet mort n'a pas été exécuté. Par exemple, ceci est possible quand vous exécutez une application à thread unique cloisonné (STA) et que le thread qui gère la file d'attente du finaliseur ne peut pas appeler depuis celle-ci.

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 y a des objets en attente de finalisation.

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

Le niveau de fragmentation est calculé comme le rapport entre l'espace libre et le total de la mémoire allouée pour la génération. Pour la génération 2, un niveau acceptable de fragmentation ne doit pas dépasser 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.

L'espace libre n'est pas une préoccupation pour la génération 0, car il s'agit de la génération où les nouveaux objets sont alloués.

La fragmentation se produit toujours dans le tas des objets volumineux, car il n'est pas compacté. Les objets libres qui sont adjacents sont réduits naturellement dans un même espace pour répondre aux requêtes d'allocation des objets volumineux.

La fragmentation peut devenir un problème avec la génération 1 et la génération 2. Si ces générations disposent d'une grande quantité d'espace libre après un garbage collection, l'utilisation des objets d'une application peut nécessiter des modifications et vous devrez 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, il est possible que trop d’objets aient été épinglés.

Si la fragmentation de la mémoire virtuelle empêche le garbage collector d'ajouter des segments, la cause peut se trouver dans la liste ci-dessous :

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

  • Des références trop nombreuses à des objets COM en cas d'interopérabilité avec du code non managé.

  • La création d'objets transitoires volumineux, ce qui entraîne l'allocation et la libération fréquentes de segments par le tas des 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 cela, utilisez la balise STARTUP_HOARD_GC_VM dans l'énumération STARTUP_FLAGS.

Contrôle des performances
Identifiez la quantité d’espace libre dans le tas managé.
Identifiez le nombre d’objets épinglés.

Si vous pensez qu'il n'existe aucune cause légitime à la fragmentation, contactez le support technique Microsoft.

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

Le garbage collection fonctionne en temps réel souple. Les applications doivent donc être en mesure de tolérer quelques pauses. L'un des critères du temps réel souple est que 95 % des opérations doivent se terminer à temps.

Avec le garbage collection simultané, les threads managés sont autorisés à s'exécuter pendant une collection, ce qui signifie que les pauses sont très minimes.

Les garbage collection éphémères (générations 0 et 1) ne durent que quelques millisecondes. La réduction du temps de pause n'est donc généralement pas possible. Toutefois, vous pouvez réduire les temps de pause des collections de génération 2 en modifiant le modèle des demandes d’allocation effectuées par une application.

Une autre méthode plus précise est celle qui consiste à utiliser les événements ETW de garbage collection. Vous pouvez rechercher le minutage des collections en ajoutant les différences de timestamp pour une séquence d’événements. L’intégralité de la séquence de collection inclut la suspension du moteur d’exécution, le garbage collection proprement dit 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 de disposer d’une collection de génération 2, et si la redirection des demandes vers un autre serveur peut atténuer les problèmes liés aux pauses.

Contrôle des performances
Identifiez la durée d’un nettoyage de la mémoire.
Déterminez ce qui a provoqué un nettoyage de la mémoire.

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, notamment si vous utilisez le garbage collection pour serveur au lieu du garbage collection pour station de travail. En effet, le seuil auquel se déclenche un garbage collection de génération 0 est plus élevé dans ces environnements, et les collections de génération 0 peuvent donc être beaucoup plus volumineuses. Les performances sont améliorées quand une application alloue plus de mémoire avant qu'un garbage collection ne 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 les garbage collection. Si un temps de processus suffisant est consacré à un garbage collection, les collections seront trop fréquent ou la collection durera trop longtemps. Un taux d'allocation d'objets plus élevé sur le tas managé entraîne des garbage collection plus fréquents. La diminution du taux d'allocation réduit la fréquence des garbage collection.

Vous pouvez surveiller les taux d'allocation à l'aide du compteur de performances Allocated Bytes/second. Pour plus d’informations, consultez Compteurs de performance 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 devra parcourir une grande quantité de mémoire si de nombreux objets restent à collecter. Le travail qui consiste à compacter les survivants prend beaucoup de temps. Pour déterminer le nombre d'objets traités au cours d'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écifique.

Contrôle des performances
Déterminez si l’utilisation élevée du processeur est provoquée par le nettoyage de la mémoire.
Définissez un point d’arrêt à la fin du nettoyage de la mémoire.

Instructions de dépannage

Cette section comporte les instructions que vous devez prendre en compte avant de commencer vos recherches.

Garbage collection pour station de travail et pour serveur

Déterminez si vous utilisez le type de garbage collection qui convient. Si votre application utilise plusieurs threads et instances d'objet, utilisez le garbage collection pour serveur au lieu du garbage collection pour station de travail. Le garbage collection pour serveur traite plusieurs threads, tandis que le garbage collection pour station de travail nécessite que plusieurs instances d'une application exécutent leurs propres threads de garbage collection et soient en concurrence pour le temps processeur.

Une application qui possède une faible charge et qui n'effectue que rarement des tâches en arrière-plan, telles qu'un service, peut utiliser le garbage collection pour station de travail en désactivant le garbage collection simultané.

Quand mesurer la taille du tas managé

Sauf si vous utilisez un profileur, vous devrez établir un modèle cohérent de mesure pour diagnostiquer efficacement les problèmes de performances. Prenez en compte les points suivants pour établir une planification :

  • Si vous effectuez la mesure après un garbage collection de génération 2, le tas managé ne contiendra aucun objet mort.
  • Si vous effectuez la mesure immédiatement après un garbage collection de génération 0, les objets de génération 1 et 2 n'auront pas encore été collectés.
  • Si vous effectuez la mesure immédiatement avant un garbage collection, vous mesurerez autant de l'allocation que possible avant le démarrage du garbage collection.
  • Effectuer une mesure pendant un garbage collection pose problème, car l'état des structures de données du garbage collector n'est pas valide pour la traversée et les résultats obtenus peuvent ne pas être complets. C'est la procédure normale.
  • Quand utilisez le garbage collection pour station de travail avec le garbage collection simultané, les objets récupérés ne sont pas compactés, donc la taille du tas peut être identique voire supérieure (la fragmentation peut le faire apparaître plus volumineux).
  • Le garbage collection simultané sur la génération 2 est différé quand la charge de mémoire physique est trop élevée.

La procédure suivante décrit comment définir un point d'arrêt pour mesurer le tas managé.

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

  • Dans le débogueur WinDbg, après avoir chargé l’extension SOS, entrez la commande suivante :

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

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

    Cette commande force un arrêt si RestartEE est exécuté après la récupération des objets de génération 2 pour le nettoyage de la mémoire.

    Dans le nettoyage de la mémoire pour serveur, un seul thread appelle RestartEE. Ainsi, le point d’arrêt se produit une seule fois pendant un nettoyage de la mémoire de génération 2.

Procédures de contrôle des performances

Cette section décrit les procédures suivantes permettant d'isoler la cause des problèmes de performances :

Pour déterminer si le problème est causé par le garbage collection

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

    • % temps dans le GC. Affiche le pourcentage de la durée calendaire 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 à libérer de l'espace dans le tas managé. 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 être inexact si le garbage collection simultané ou le garbage collection d’arrière-plan sont impliqués.

    • 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 trop élevée par rapport à la mémoire totale utilisée par votre application.

    La plupart des compteurs de performances de mémoire sont mis à jour à la fin de chaque garbage collection. Ils peuvent donc ne pas refléter les conditions actuelles à propos desquelles vous voulez obtenir des informations.

Pour déterminer si l'exception de mémoire insuffisante est managée

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

    !pe

    Si l'exception est managée, OutOfMemoryException s'affiche comme le 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 de quel thread provient l'exception de mémoire insuffisante. Entrez la commande suivante dans le débogueur pour afficher tous les threads avec leurs piles d’appels :

    ~\*kb

    Le thread avec la pile associée aux appels d'exception est indiqué par l'argument RaiseTheException. Il s'agit de l'objet exception managée.

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

    !pe -nested

    Si vous ne trouvez pas d'exceptions, l'exception de mémoire insuffisante provient de code non managé.

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

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

    !address -summary

    La plus grande région libre est affichée comme dans la sortie suivante.

    Largest free region: Base 54000000 - Size 0003A980
    

    Dans cet exemple, la taille de la région libre la plus grande 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-

  • Utiliser la commande vmstat :

    !vmstat

    La région libre la plus grande correspond à la plus grande valeur de la colonne MAXIMUM, comme illustré 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 si la mémoire physique est suffisante

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

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

    Si Total est proche de Limit, la mémoire physique devient insuffisante.

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

  • Utilisez le compteur de performances de mémoire # Total committed bytes pour obtenir le nombre d'octets que le tas managé valide. Le garbage collector valide des parties d'un segment au fur et à mesure des besoins, et non tous en même temps.

    Notes

    N'utilisez pas le compteur de performances # 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 à la taille de son seuil, c'est-à-dire, à la taille qui déclenche un garbage collection si la génération est remplie d'objets. Cette valeur est donc généralement égale à zéro.

Pour déterminer la quantité de mémoire réservée pour le tas managé

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

    Le récupérateur de mémoire réserve de la mémoire sous forme de segments, et vous pouvez déterminer où démarre un segment à l’aide de la commande eeheap.

    Important

    Même s'il est possible de déterminer la quantité de mémoire que le garbage collector alloue à chaque segment, la taille d'un segment dépend de l'implémentation et est susceptible de changer à tout moment, y compris lors des 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 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é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 SOS chargée, entrez la commande suivante :

    !dumpheap –stat

    Si le tas managé est volumineux, exécuter dumpheap peut prendre un certain temps.

    Vous pouvez commencer l'analyse des dernières lignes de la sortie, car elles contiennent 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 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 dont la taille est comprise entre 150 et 200 octets, entrez la commande suivante :

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

    Voici un exemple de résultat.

    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 la centralisation des chaînes. Pour plus d'informations sur la centralisation des chaînes, voir la rubrique de référence sur la méthode String.Intern.

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

  • Dans le débogueur WinDbg avec l’extension SOS chargée, entrez la commande suivante pour obtenir la liste des références aux objets :

    !gcroot

    -ou-

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

    !gcroot 1c37b2ac

    Les racines trouvées sur les piles peuvent être de 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é par le garbage collection est un objet dynamique. Cela signifie qu’une racine maintient directement ou indirectement l’objet. gcroot doit donc retourner des informations de chemin d’accès à l’objet. Vous devez examiner les graphiques retournés et voir pourquoi ces objets sont encore référencés.

Pour déterminer si un finaliseur a été exécuté

  • Exécutez un programme de test contenant 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 de ces objets avaient été interrompus. La méthode GC.WaitForPendingFinalizers permet aux finaliseurs d'effectuer leurs tâches et résout le problème.

Pour déterminer s'il existe des objets en attente de finalisation

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

    !finalizequeue

    Regardez le nombre d'objets qui sont prêts pour la finalisation. Si le nombre est élevé, vous devez examiner les raisons pour lesquelles ces finaliseurs ne peuvent pas progresser du tout ou pas assez rapidement.

  2. Pour obtenir une sortie des threads, entrez 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, est en cours d'exécution, le cas échéant. Quand un thread finaliseur n'exécute pas de finaliseur, il attend qu'un événement lui demande d'effectuer son travail. La plupart du temps, vous verrez le thread finaliseur dans cet état, car il s'exécute avec une THREAD_HIGHEST_PRIORITY et est supposé terminer l'exécution des 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 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 dans la génération 0, entrez la commande suivante pour obtenir des informations sur la 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 fait un dump pour 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 du tas consacrée à la génération 0 utilise 9 Mo d'espace pour les objets et dispose de 7 Mo d'espace libre. Cette analyse montre 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, car elle est la cause de la 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 SOS chargée, entrez la commande suivante :

    !gchandles

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

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

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

  • Regardez ce qu'affiche le compteur de performances de mémoire % Time in GC.

    La valeur est calculée à partir d'un échantillon d'intervalle de temps. Les compteurs sont mis à jour à la fin de chaque garbage collection. L'exemple actuel aura donc la même valeur que l'exemple précédent si aucune collection ne s'est produite pendant l'intervalle.

    La durée de la collection est obtenue en multipliant l'échantillon d'intervalle de temps par la valeur de pourcentage.

    Les données suivantes montrent quatre échantillons d'intervalles de deux secondes, pour une étude de 8 secondes. Les colonnes Gen0, Gen1et Gen2 indiquent le nombre total de collectes de mémoires 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 n'affichent pas le moment auquel s'est produit le garbage collection, mais vous pouvez toutefois connaître le nombre de garbage collection qui se sont produits dans un intervalle de temps. Dans le pire des cas, le dixième nettoyage de la mémoire de génération 0 s’est terminé au début du deuxième intervalle et le onzième nettoyage de la mémoire de génération 0 s’est terminé à la fin du troisième intervalle. Le délai entre la fin du dixième et la fin du onzième garbage collection est d'environ 2 secondes. Le compteur de performances indique 3 %. La durée du onzième garbage collection de génération 0 était donc de (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 nettoyage de la mémoire de génération 2 a démarré pendant le quatrième intervalle et s’est terminé au cinquième intervalle. Dans le pire des cas, le dernier nettoyage de la mémoire était pour une collection de génération 0 qui s’est terminée au début du troisième intervalle et le nettoyage de la mémoire de génération 2 s’est terminé à la fin du cinquième intervalle. Le délai entre la fin du garbage collection de génération 0 et la fin du garbage collection de génération 2 est donc de 4 secondes. Étant donné que le compteur % Time in GC affiche 20 %, la durée maximale du garbage collection de génération 2 est de (4 secondes * 20 % = 800 ms).

  • Vous pouvez également déterminer la durée d’un garbage collection à l’aide des événements ETW de garbage collection en analysant les informations fournies.

    Par exemple, les données suivantes indiquent une séquence d’événements qui se sont produits 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 des threads managés a pris 26 us (GCSuspendEEEndGCSuspendEEBegin_V1).

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

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

    La sortie suivante fournit un exemple de garbage collection d'arrière-plan et inclut les champs de processus, de thread et 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 à la ligne 42504816 indique qu'il s'agit d'un garbage collection d'arrière-plan, car le dernier champ est 1. Cela devient le nettoyage de la mémoire numéro 102019.

    L'événement GCStart se produit, car un garbage collection éphémère doit être effectué avant de démarrer un garbage collection d'arrière-plan. Cela devient le nettoyage de la mémoire numéro 102020.

    À la ligne 42514170, le nettoyage de la mémoire numéro 102020 termine. Les threads managés sont redémarrés à cet endroit. L'opération se termine au thread 4372, qui a déclenché ce garbage collection d'arrière-plan.

    Au thread 4744, une suspension se produit. C'est la seule fois 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 se trouve à la ligne 89931423. Cela signifie que le garbage collection d'arrière-plan a duré environ 47 secondes ((89931423-42504816)/1000).

    Pendant que les threads managés sont exécutés, vous pouvez voir tous les garbage collection éphémères 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, entrez 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 des appels sera similaire, à ceci près que le thread sera le thread finaliseur. Le thread finaliseur reçoit une notification asynchrone de mémoire insuffisante et déclenche le garbage collection.

    Si le garbage collection a été provoqué par l'allocation de mémoire, la pile se présente comme ceci :

    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 GCHeap::GarbageCollectGeneration. Si vous déterminez que les garbage collection de génération 2 sont dus à des allocations, vous devez déterminer quels objets sont collectés par les garbage collection de génération 2 et comment 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, ainsi que les objets qui ont provoqué la collection 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 montrer 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 montrer 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é. Ces objets double[] étaient donc à l'origine du déclenchement du garbage collection de génération 2. L'étape suivante consiste à déterminer pourquoi les objets double[] y sont situés et pourquoi ils sont morts. Vous pouvez demander à un développeur d’où proviennent ces objets ou vous pouvez utiliser la commande gcroot.

Pour déterminer si l’utilisation élevée du processeur est causée par le garbage collection

  • Mettez en corrélation la valeur du compteur de performances de mémoire % Time in GC avec le temps d'exécution.

    Si la valeur % Time in GC connaît un pic en même temps que le temps d'exécution, le garbage collection provoque une utilisation élevée du processeur. Dans le cas contraire, profilez l'application pour trouver où se produit l'utilisation élevée.

Voir aussi