Partager via


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'"

    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

  1. 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):
    
  2. 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 
    
  3. 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

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

  2. 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.

    RemarqueRemarque

    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

  1. 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.

  2. 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.

Voir aussi

Concepts

Garbage Collection