Surveillance Garbage Collector en C#
J'ai une application WPF qui rencontre beaucoup de problèmes de performances. Le pire d'entre eux est que, parfois, l'application se fige pendant quelques secondes avant de la remettre en marche.
Je suis actuellement en train de débogage de l'application pour voir ce que ce gel peut être lié à, et je crois que l'une des choses qui peuvent être à l'origine c'est le Garbage Collector. Depuis mon application est en cours d'exécution dans une mesure très limitée de l'environnement, je crois que le Garbage Collector peut être en utilisant toutes les ressources de la machine quand il est exécuté et laissant aucun, à notre application.
À les vérifier des hypothèses, j'ai trouvé ces articles: La Collecte Des Ordures Notifications et La Collecte des ordures Notifications .NET 4.0, qui expliquent comment faire ma demande peut être notifié lorsque le Garbage Collector va commencer à courir et quand c'est fini.
Sur la base de ces articles, j'ai créé la catégorie ci-dessous pour obtenir les 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é le gcConcurrent option pour 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 me semble que si aucune notification n'est envoyée que le Garbage Collector sera exécuté. J'ai mis des points d'arrêt dans le DoGCMonitoring et il apparaît que les conditions (s == GCNotificationStatus.Réussi) et (s == GCNotificationStatus.Réussi) ne sont jamais satisfaits, par conséquent, le contenu de ceux-fi états ne sont jamais exécutées.
Ce que je fais mal?
Note: je suis à l'aide de C# avec WPF et de l' .NET Framework 3.5.
Mise à JOUR de 1
Mis à jour mon GCMonitor test avec le AllocationTest méthode. Cette méthode est utilisée pour des fins de test uniquement. Je voulais juste vous assurer que suffisamment de mémoire a été allouée pour forcer le Garbage Collector à s'exécuter.
Mise à JOUR 2
Mis à jour le DoGCMonitoring méthode, avec de nouvelles vérifications sur le retour de l'méthodes WaitForFullGCApproach et WaitForFullGCComplete. De ce que j'ai vu jusqu'à présent, mon application qui va directement à l' (s == GCNotificationStatus.NotApplicable) condition. Donc, je pense que j'ai quelques problème de configuration quelque part qui m'en empêche d'obtenir les résultats souhaités.
La documentation de la GCNotificationStatus enum peut être trouvé ici.
- Avez-vous essayé en fait de profilage avec un outil, dire quelque chose comme l'analyseur de performances de windows ou windbg au lieu d'essayer d'écrire un GC wrapper?
- Peut-être que la GC n'est pas en cours d'exécution (encore). Pouvez-vous montrer AllocationTest() ?
- Salut, en fait, je dois avoir un outil de profilage, cependant le gel problème que j'ai mentionné plus tôt qui se passe dans un environnement de production, et non pas sur ma machine (je ne peux pas le reproduire). Et malheureusement pour moi je ne peut pas exécuter l'outil de profilage dans la production de l'environnement.
- Si vous pensez que c'est la GC, vous pouvez essayer de créer une machine virtuelle avec très peu de RAM (dire, WinXP avec 256 mo de mémoire) et l'exécution de votre demande. Si le problème réapparaît, augmenter la quantité de RAM disponible et essayez à nouveau. Aussi, il est normal que le GC de congeler vos processus pendant qu'il fait sa chose. Après tout, il a besoin d'un état figé affaires dans le but de parcourir le graphe d'objets et de trouver des éléments admissibles pour la collecte.
- Quelles preuves avez-vous que GC est le problème?
- c'est techniquement incorrect. Le serveur GC (utilisé dans ASP.NET par exemple) est un cessez-le-monde garbage collector. La station de travail GC (utilisé dans WPF, et la plupart des autres applications de bureau) est une concurrente GC - il n'arrête pas le monde.
- Oh... je ne savais pas... je me demande comment il peut faire sa chose si l'objet graphique ne cesse de changer de dessous?
- L' .NET simultanées GC, comme la JVM simultanées GC, s'arrête toujours du monde pour la première marque. Autant que je sache, la seule x86 sans un tas proportionnelle arrêter le monde est Azul Zing. Vous pouvez lire un peu plus à propos de " stop-le-monde en simultané JVM ici... blog.griddynamics.com/2011/06/...
Vous devez vous connecter pour publier un commentaire.
Je ne vois pas
GC.RegisterForFullGCNotification(int,int)
n'importe où dans votre code. On dirait que vous êtes à l'aide de laWaitForFullGC[xxx]
méthodes, mais ne sont jamais en vous inscrivant pour la notification. C'est sans doute pourquoi vous êtes obtenir les NotApplicable état.Cependant, je suis douter que GC est votre problème, si elle est possible, je suppose qu'il serait bon de savoir sur l'ensemble de la GC modes et les meilleures façons de déterminer ce qui se passe. Il existe deux modes de Collecte des Ordures dans .NET: le Serveur et poste de travail. Ils collectent les mêmes mémoire inutilisée, cependant, la façon dont c'est fait est jamais si légèrement différente.
Server Version - Ce mode indique la GC pour vous utilisez un serveur d'application côté, et il essaie d'optimiser les collections pour ces scénarios. Il va diviser le tas en plusieurs sections, 1 par UC. Lorsque le GC est démarré, il va exécuter un thread sur chaque PROCESSEUR en parallèle. Vous voulez vraiment plusieurs Processeurs pour que cela fonctionne bien. Alors que la version du serveur utilise plusieurs threads pour le GC, c'est pas la même que la concurrente de la station de travail GC mode énumérés ci-dessous. Chaque thread agit comme le non-cumul version.
Workstation Version - Ce mode indique GC vous utilisez un client de l'application côté. Il les chiffres que vous avez des ressources plus limitées que la version du Serveur, et donc il y a un seul GC fil. Cependant, il existe deux configurations de la station de travail version: simultanée et non simultanée.
Vous ne pouvez pas vous inscrire pour les notifications sur le simultanées collector, puisque c'est fait en arrière-plan. Il est possible que votre application n'est pas en utilisant la concurrente de collecteur (j'ai remarqué que vous avez le
gcConcurrent
désactivé dans leapp.config
, mais il semble que c'est uniquement pour les tests?). Si c'est le cas, vous pouvez certainement voir votre demande de congélation si il ya de fortes collections. C'est pourquoi ils ont créé la concurrente de collecteur. Le type de GC mode peut être en partie définis dans le code, et entièrement mise en application des configurations et de la configuration de la machine.Que pouvons-nous faire pour savoir exactement ce que notre application est d'utiliser? Au moment de l'exécution, vous pouvez faire une requête de la statique
GCSettings
classe (dansSystem.Runtime
).GCSettings.IsServerGC
vous dira si vous utilisez la station de travail sur les versions serveur etGCSettings.LatencyMode
peux vous dire que si vous utilisez le simultanées, non simultanées ou un spécial, vous avez à mettre dans le code, ce qui n'est pas vraiment applicable ici. Je pense que ce serait un bon endroit pour commencer, et pourrait expliquer pourquoi il est en cours d'exécution fine sur votre machine, mais pas la production.Dans les fichiers de configuration,
<gcConcurrent enabled="true|false"/>
ou<gcServer enabled="true|false"/>
contrôle les modes du garbage collector. Gardez à l'esprit ce qui peut être dans votre application.fichier de configuration (situé à côté de l'assembly en cours d'exécution) ou dans la machine.fichier de config qui se trouve dans%windir%\Microsoft.NET\Framework\[version]\CONFIG\
Vous pouvez également utiliser à distance l'analyseur de Performances de Windows pour accéder à la machine de production de compteurs de performances pour .NET de la collecte des ordures et la vue de ces statistiques. Vous pouvez faire de même avec le suivi d'Événements pour Windows (ETW) tous à distance. Pour l'analyseur de performances, vous voulez le
.NET CLR Memory
objet, et sélectionnez votre application dans la liste des instances de la boîte.