Surveillance du ramasseur D'ordures en C#

j'ai une application WPF qui connaît beaucoup de problèmes de performance. Le pire d'entre eux est que parfois l'application gèle juste quelques secondes avant de recommencer.

je suis en train de déboguer l'application pour voir à quoi ce gel pourrait être lié, et je crois que l'une des choses qui pourrait en être la cause est le collecteur D'ordures. Puisque mon application fonctionne dans un environnement très limité, je crois que le collecteur D'ordures peut utiliser tous les les ressources de la machine quand elle est lancée et ne laissant aucune à notre application.

À vérifier cette hypothèses, j'ai trouvé ces articles: Notification De Collecte Des Ordures et notification de collecte des ordures dans .NET 4.0, qui explique comment mon application peut être informée quand le collecteur d'ordures commencera à fonctionner et quand il sera terminé.

donc, basé sur ces articles j'ai créé la classe ci-dessous pour obtenir le notifications:

public sealed class GCMonitor
{
    private static volatile GCMonitor instance;
    private static object syncRoot = new object();

    private Thread gcMonitorThread;
    private ThreadStart gcMonitorThreadStart;

    private bool isRunning;

    public static GCMonitor GetInstance()
    {
        if (instance == null)
        {
            lock (syncRoot)
            {
                instance = new GCMonitor();
            }
        }

        return instance;
    }

    private GCMonitor()
    {
        isRunning = false;
        gcMonitorThreadStart = new ThreadStart(DoGCMonitoring);
        gcMonitorThread = new Thread(gcMonitorThreadStart);
    }

    public void StartGCMonitoring()
    {
        if (!isRunning)
        {
            gcMonitorThread.Start();
            isRunning = true;
            AllocationTest();
        }
    }

    private void DoGCMonitoring()
    {
        long beforeGC = 0;
        long afterGC = 0;

        try
        {

            while (true)
            {
                // Check for a notification of an approaching collection.
                GCNotificationStatus s = GC.WaitForFullGCApproach(10000);
                if (s == GCNotificationStatus.Succeeded)
                {
                    //Call event
                    beforeGC = GC.GetTotalMemory(false);
                    LogHelper.Log.InfoFormat("===> GC <=== " + Environment.NewLine + "GC is about to begin. Memory before GC: %d", beforeGC);
                    GC.Collect();

                }
                else if (s == GCNotificationStatus.Canceled)
                {
                    LogHelper.Log.Info("===> GC <=== " + Environment.NewLine + "GC about to begin event was cancelled");
                }
                else if (s == GCNotificationStatus.Timeout)
                {
                    LogHelper.Log.Info("===> GC <=== " + Environment.NewLine + "GC about to begin event was timeout");
                }
                else if (s == GCNotificationStatus.NotApplicable)
                {
                    LogHelper.Log.Info("===> GC <=== " + Environment.NewLine + "GC about to begin event was not applicable");
                }
                else if (s == GCNotificationStatus.Failed)
                {
                    LogHelper.Log.Info("===> GC <=== " + Environment.NewLine + "GC about to begin event failed");
                }

                // Check for a notification of a completed collection.
                s = GC.WaitForFullGCComplete(10000);
                if (s == GCNotificationStatus.Succeeded)
                {
                    //Call event
                    afterGC = GC.GetTotalMemory(false);
                    LogHelper.Log.InfoFormat("===> GC <=== " + Environment.NewLine + "GC has ended. Memory after GC: %d", afterGC);

                    long diff = beforeGC - afterGC;

                    if (diff > 0)
                    {
                        LogHelper.Log.InfoFormat("===> GC <=== " + Environment.NewLine + "Collected memory: %d", diff);
                    }

                }
                else if (s == GCNotificationStatus.Canceled)
                {
                    LogHelper.Log.Info("===> GC <=== " + Environment.NewLine + "GC finished event was cancelled");
                }
                else if (s == GCNotificationStatus.Timeout)
                {
                    LogHelper.Log.Info("===> GC <=== " + Environment.NewLine + "GC finished event was timeout");
                }
                else if (s == GCNotificationStatus.NotApplicable)
                {
                    LogHelper.Log.Info("===> GC <=== " + Environment.NewLine + "GC finished event was not applicable");
                }
                else if (s == GCNotificationStatus.Failed)
                {
                    LogHelper.Log.Info("===> GC <=== " + Environment.NewLine + "GC finished event failed");
                }

                Thread.Sleep(1500);
            }
        }
        catch (Exception e)
        {
            LogHelper.Log.Error("  ********************   Garbage Collector Error  ************************ ");
            LogHelper.LogAllErrorExceptions(e);
            LogHelper.Log.Error("  -------------------   Garbage Collector Error  --------------------- ");
        }
    }

    private void AllocationTest()
    {
        // Start a thread using WaitForFullGCProc.
        Thread stress = new Thread(() =>
        {
            while (true)
            {
                List<char[]> lst = new List<char[]>();

                try
                {
                    for (int i = 0; i <= 30; i++)
                    {
                        char[] bbb = new char[900000]; // creates a block of 1000 characters
                        lst.Add(bbb);                // Adding to list ensures that the object doesnt gets out of scope
                    }

                    Thread.Sleep(1000);
                }
                catch (Exception ex)
                {
                    LogHelper.Log.Error("  ********************   Garbage Collector Error  ************************ ");
                    LogHelper.LogAllErrorExceptions(e);
                    LogHelper.Log.Error("  -------------------   Garbage Collector Error  --------------------- ");
                }
            }


        });
        stress.Start();
    }
}

et j'ai ajouté l'option gcConcurrent à mon application.fichier de configuration (ci-dessous):

<?xml version="1.0"?>
<configuration>
  <configSections>
    <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net-net-2.0"/>
  </configSections>

  <runtime>
    <gcConcurrent enabled="false" />
  </runtime>

  <log4net>
    <appender name="Root.ALL" type="log4net.Appender.RollingFileAppender">
      <param name="File" value="../Logs/Root.All.log"/>
      <param name="AppendToFile" value="true"/>
      <param name="MaxSizeRollBackups" value="10"/>
      <param name="MaximumFileSize" value="8388608"/>
      <param name="RollingStyle" value="Size"/>
      <param name="StaticLogFileName" value="true"/>
      <layout type="log4net.Layout.PatternLayout">
      <param name="ConversionPattern" value="%date [%thread] %-5level - %message%newline"/>
      </layout>
    </appender>
    <root>
      <level value="ALL"/>
      <appender-ref ref="Root.ALL"/>
    </root>
  </log4net>

  <appSettings>
    <add key="setting1" value="1"/>
    <add key="setting2" value="2"/>
  </appSettings>
  <startup>
    <supportedRuntime version="v2.0.50727"/>
  </startup>

</configuration>

Cependant, chaque fois que l'application est exécutée, il semble que si aucune notification est envoyée que le collecteur d'ordures va courir. J'ai mis des points de rupture dans le suivi des chiens et il semble que les conditions (s == GCNotificationStatus.Réussi) et (s == GCNotificationStatus.Réussi) ne sont jamais satisfaits, donc le contenu de ces déclarations ifs ne sont jamais exécuter.

Ce que je fais mal?

Note: j'utilise C# avec WPF et le .net Framework 3.5.

UPDATE 1

mise à jour de mon test GCMonitor avec la méthode AllocationTest. Cette méthode est utilisée pour des fins de test uniquement. Je voulais juste m'assurer qu'il y avait assez de mémoire pour forcer le Éboueur à s'enfuir.

UPDATE 2

mise à jour de la méthode de suivi DOGC, avec de nouveaux contrôles sur le retour des méthodes Waitfullgcapproach et Waitfullgcomplete. D'après ce que j'ai vu jusqu'à présent, mon application va directement au statut (s == GCNotificationStatus.NotApplicable). Donc je pense que j'ai une mauvaise configuration quelque part qui m'empêche d'obtenir les résultats désirés.

<!-La documentation pour le GCNotificationStatus enum peut être trouvée ici.

29
demandé sur Felipe 2012-03-12 19:30:25

1 réponses

je ne vois pas GC.RegisterForFullGCNotification(int,int) n'importe où dans votre code. On dirait que vous êtes à l'aide de la WaitForFullGC[xxx] mais ne s'inscrivent jamais pour la notification. C'est probablement pour ça que vous avez le statut de non-opposable.

cependant, je doute que la GC soit votre problème, bien que ce soit possible, je suppose qu'il serait bon de connaître tous les modes de la GC qui existent et les meilleures façons de déterminer ce qui se passe. Il y a deux modes de collecte des ordures dans .NET: le serveur et le poste de travail. Ils recueillent tous les deux la même mémoire inutilisée, cependant la façon dont il est fait est si légèrement différent.

  • Version Du Serveur - ce mode indique au GC que vous utilisez une application côté serveur, et il essaie d'optimiser les collections pour ces scénarios. Il divisera le tas en plusieurs sections, 1 par CPU. Lorsque le GC est lancé, il exécute un thread sur chaque CPU en parallèle. Vous voulez vraiment plusieurs CPU pour que cela fonctionne bien. Bien que la version du serveur utilise plusieurs threads pour le GC, ce n'est pas la même chose que le mode GC du poste de travail concurrent indiqué ci-dessous. Chaque thread agit comme la version non-concurrente.

  • Version Poste De Travail - ce mode indique au GC que vous utilisez une application côté client. Il suppose que vous avez plus de ressources limitées que la version du serveur, et il n'y a donc qu'un seul thread GC. Cependant, il existe deux configurations de la version poste de travail: concurrent et non simultanées.

    • Simultanées - c'est la version activée par défaut chaque fois que le poste de travail GC est utilisé (ce serait le cas pour votre application WPF). Le GC tourne toujours sur un thread séparé qui marque toujours les objets pour la collecte lorsque l'application tourne. De plus, elle choisit de compacter ou non la mémoire dans certaines générations, et fait ce choix en fonction de la performance. Il doit encore tout congeler. threads pour exécuter une collection si le compactage est fait, mais vous ne verrez presque jamais une application sans réponse en utilisant ce mode. Cela crée une meilleure expérience interactive pour les utilisations et est le meilleur pour la console ou les applications GUI.
    • Non-Concurrent - C'est une version que vous pouvez configurer votre application pour utiliser, si vous le souhaitez. Dans ce mode, le fil GC dort jusqu'à ce qu'un GC soit lancé, puis il va et marque Tous les arbres d'objet qui sont des déchets, libère la mémoire, et le compacte, le tout pendant que tous les autres fils sont suspendus. Cela peut faire en sorte que l'application devienne parfois insensible pour un court période de temps.

vous ne pouvez pas vous inscrire pour les notifications sur le collecteur concurrent, puisque c'est fait en arrière-plan. Il est possible que votre application n'utilise pas le collecteur concurrent (je remarque que vous avez le gcConcurrent désactivé app.config, mais il semble que c'est uniquement pour les tests?). Si c'est le cas, vous pouvez certainement voir votre application geler s'il ya des collections lourdes. C'est pourquoi ils ont créé la concurrente de collecteur. Le type de mode GC peut être partiellement réglé en code, et entièrement réglé dans les configurations d'application et de machine.

Que pouvons-nous faire pour comprendre exactement ce que notre application utilise? Au moment de l'exécution, vous pouvez faire une requête de la statique GCSettings la classe (dans System.Runtime). GCSettings.IsServerGC vous dira si vous utilisez la station de travail sur les versions de serveur et GCSettings.LatencyMode peut vous dire si vous utilisez le concurrent, non-concurrent ou un spécial que vous devez définir en code qui n'est pas vraiment applicable ici. Je pense que ce serait un bon endroit pour commencer, et pourrait expliquer pourquoi il fonctionne bien sur votre machine, mais pas la production.

Dans les fichiers de configuration <gcConcurrent enabled="true|false"/> ou <gcServer enabled="true|false"/> contrôler les modes du collecteur d'ordures. Gardez à l'esprit ce qui peut être dans votre application.fichier de configuration (situé à côté du l'exécution de l'assemblée) ou dans la machine.fichier de configuration, qui se trouve dans %windir%\Microsoft.NET\Framework\[version]\CONFIG\

vous pouvez également utiliser à distance le moniteur de performances Windows pour accéder aux compteurs de performances de la machine de production pour la collecte des ordures.net et voir ces statistiques. Vous pouvez faire la même chose avec le traçage D'événements pour Windows (ETW) à distance. Pour le moniteur de performance, vous voulez le .NET CLR Memory objet et sélectionnez votre application dans la liste des instances de la boîte.

39
répondu Christopher Currens 2012-03-12 17:11:42