Monitoraggio di Garbage Collector in C #

Ho un’applicazione WPF che sta riscontrando un sacco di problemi di prestazioni. La cosa peggiore è che a volte l’applicazione si blocca solo per pochi secondi prima di ricominciare.

Attualmente sto eseguendo il debug dell’applicazione per vedere a cosa potrebbe essere collegato questo blocco e credo che una delle cose che potrebbe causarlo sia il Garbage Collector. Poiché la mia applicazione è in esecuzione in un ambiente molto limitato, ritengo che Garbage Collector possa utilizzare tutte le risorse della macchina quando viene eseguita e non ne lascia alcuna alla nostra applicazione.

Per verificare queste ipotesi ho trovato questi articoli: notifiche di Garbage Collection e notifiche di Garbage Collection in .NET 4.0 , che spiegano come la mia applicazione può essere notificata quando il Garbage Collector inizierà a funzionare e quando sarà finito.

Quindi, in base a quegli articoli ho creato la class qui sotto per ottenere le notifiche:

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  GC  GC  GC  GC  GC  0) { LogHelper.Log.InfoFormat("===> GC  GC  GC  GC  GC  { while (true) { List lst = new List(); 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(); } } 

E ho aggiunto l’opzione gcConcurrent al mio file app.config (sotto):

    

Tuttavia, ogni volta che l’applicazione viene eseguita, sembra che non venga inviata alcuna notifica che verrà eseguito il Garbage Collector. Ho inserito i breakpoint in DoGCMonitoring e sembra che le condizioni (s == GCNotificationStatus.Succeeded) e (s == GCNotificationStatus.Succeeded) non siano mai soddisfatte, quindi il contenuto di queste istruzioni ifs non viene mai eseguito.

Che cosa sto facendo di sbagliato?

Nota: sto usando C # con WPF e .NET Framework 3.5.

AGGIORNAMENTO 1

Aggiornato il mio test GCMonitor con il metodo AllocationTest. Questo metodo è solo a scopo di test. Volevo solo assicurarmi che fosse stata allocata abbastanza memoria per forzare l’esecuzione di Garbage Collector.

AGGIORNAMENTO 2

Aggiornato il metodo DoGCMonitoring, con nuovi controlli sul ritorno dei metodi WaitForFullGCApproach e WaitForFullGCComplete. Da quanto ho visto finora, la mia applicazione passa direttamente alla condizione (s == GCNotificationStatus.NotApplicable). Quindi penso di avere qualche errore di configurazione da qualche parte che mi impedisce di ottenere i risultati desiderati.

La documentazione per l’enumerazione GCNotificationStatus può essere trovata qui .

Non vedo GC.RegisterForFullGCNotification(int,int) qualsiasi parte del codice. Sembra che tu stia utilizzando i WaitForFullGC[xxx] , ma non ti stai mai registrando per la notifica. Questo è probabilmente il motivo per cui ottieni lo stato NotApplicable.

Tuttavia, dubito che GC sia il tuo problema, mentre ansible, suppongo che sarebbe bello sapere di tutte le modalità GC che ci sono e dei modi migliori per determinare cosa sta succedendo. Esistono due modalità di Garbage Collection in .NET: il server e la workstation. Entrambi collezionano la stessa memoria inutilizzata, tuttavia il modo in cui è fatto è sempre leggermente diverso.

  • Versione server : questa modalità indica al GC che stai utilizzando un’applicazione lato server e tenta di ottimizzare le raccolte per questi scenari. Dividerà l’heap in diverse sezioni, 1 per CPU. All’avvio del GC, verrà eseguito un thread su ciascuna CPU in parallelo. Vuoi davvero più CPU per far funzionare tutto questo. Mentre la versione del server utilizza più thread per il GC, non è uguale alla modalità GC della workstation simultanea elencata di seguito. Ogni thread si comporta come la versione non simultanea.

  • Versione workstation : questa modalità indica a GC che stai utilizzando un’applicazione client. Immagina di avere risorse più limitate rispetto alla versione Server, e quindi c’è solo un thread GC. Tuttavia, esistono due configurazioni della versione Workstation: concurrent e non-concurrent.

    • Concorrente – Questa è la versione triggersta per impostazione predefinita ogni volta che viene utilizzato il GC della workstation (questo è il caso della tua applicazione WPF). Il GC è sempre in esecuzione su un thread separato che contrassegna sempre gli oggetti per la raccolta quando l’applicazione è in esecuzione. Inoltre, sceglie se compattare o meno la memoria in determinate generazioni e fa quella scelta in base alle prestazioni. È ancora necessario congelare tutti i thread per eseguire una raccolta se la compattazione è stata eseguita, ma non si vedrà quasi mai un’applicazione che non risponde quando si utilizza questa modalità. Ciò crea una migliore esperienza intertriggers per gli usi ed è la soluzione migliore per le app della console o della GUI.
    • Non concomitante : si tratta di una versione che è ansible configurare per l’applicazione da utilizzare, se lo si desidera. In questa modalità, il thread GC rimane in attesa finché non viene avviato un GC, quindi passa e contrassegna tutti gli alberi object che sono spazzatura, libera la memoria e la compatta, mentre tutti gli altri thread sono sospesi. Ciò può causare a volte che l’applicazione non risponda per un breve periodo di tempo.

Non è ansible registrarsi per le notifiche sul raccoglitore concorrente, poiché ciò avviene in background. È ansible che la tua applicazione non stia utilizzando il collector simultaneo (noto che hai gcConcurrent disabilitato app.config , ma sembra che sia solo per i test?). Se questo è il caso, puoi sicuramente vedere l’applicazione bloccarsi se ci sono raccolte pesanti. Questo è il motivo per cui hanno creato il collezionista simultaneo. Il tipo di modalità GC può essere parzialmente impostato nel codice e impostato completamente nelle configurazioni dell’applicazione e nelle configurazioni della macchina.

Cosa possiamo fare per capire esattamente cosa sta usando la nostra applicazione? In fase di esecuzione, è ansible eseguire una query sulla class GCSettings statica (in System.Runtime ). GCSettings.IsServerGC ti dirà se stai eseguendo la workstation su versioni server e GCSettings.LatencyMode può dirti se stai utilizzando il concurrent, non-concurrent o uno speciale che devi impostare in codice che non è realmente applicabile Qui. Penso che sarebbe un buon punto di partenza, e potrei spiegare perché sta funzionando bene sulla tua macchina, ma non sulla produzione.

Nei file di configurazione, o controllano le modalità del garbage collector. Tieni presente che questo può essere nel file app.config (situato accanto all’assembly in esecuzione) o nel file machine.config, che si trova in %windir%\Microsoft.NET\Framework\[version]\CONFIG\

È inoltre ansible utilizzare in remoto Performance Monitor di Windows per accedere ai contatori delle prestazioni della macchina di produzione per la raccolta dati obsoleti .NET e visualizzare tali statistiche. È ansible fare lo stesso con Event Tracing per Windows (ETW) in remoto. Per il monitor delle prestazioni, è necessario l’object di .NET CLR Memory e selezionare l’applicazione nella casella di riepilogo delle istanze.