Garbage Collection e prestazioni
Questo articolo descrive i problemi relativi all'utilizzo di Garbage Collection e della memoria. Sono incluse informazioni per risolvere i problemi che riguardano l'heap gestito e che descrivono come ridurre al minimo l'effetto di Garbage Collection sulle applicazioni. Per ogni problema sono disponibili collegamenti alle procedure che è possibile usare per approfondimenti aggiuntivi.
Strumenti di analisi delle prestazioni
Le sezioni seguenti descrivono gli strumenti disponibili per esaminare i problemi relativi a utilizzo della memoria e Garbage Collection. Le procedure illustrate più avanti in questo articolo fanno riferimento a questi strumenti.
Contatori delle prestazioni di memoria
È possibile usare i contatori delle prestazioni per raccogliere dati sulle prestazioni. Per istruzioni, vedere Profilatura runtime. La categoria Memoria CLR .NET dei contatori delle prestazioni, descritta in Contatori delle prestazioni in .NET, fornisce informazioni su Garbage Collector.
Debug con SOS
È possibile usare il debugger di Windows (WinDbg) per controllare gli oggetti nell'heap gestito.
Per installare WinDbg, installare gli strumenti di debug per Windows dalla pagina di download degli strumenti di debug per Windows.
Eventi ETW di Garbage Collection
Event Tracing for Windows (ETW) è un sistema di traccia che integra il supporto per profilatura e debug offerto da .NET. A partire da .NET Framework 4, gli eventi ETW di Garbage Collection acquisiscono informazioni utili per l'analisi dell'heap gestito da un punto di vista statistico. Ad esempio, l'evento GCStart_V1
, generato quando sta per verificarsi un'operazione di Garbage Collection, fornisce le informazioni seguenti:
- Generazione di oggetti raccolta.
- Che cosa ha attivato l'operazione di Garbage Collection.
- Tipo di Garbage Collection (simultanea o non simultanea).
La registrazione degli eventi ETW è efficiente e non maschera alcun problema di prestazioni associato a Garbage Collection. Un processo può fornire i propri eventi insieme agli eventi ETW. Una volta registrati, sia gli eventi dell'applicazione sia quelli di Garbage Collection possono essere correlati per determinare come e quando si verificano problemi relativi all'heap. Ad esempio, un'applicazione server può fornire eventi all'inizio e alla fine di una richiesta client.
API di profilatura
Le interfacce di profilatura Common Language Runtime (CLR) forniscono informazioni dettagliate sugli oggetti interessati durante l'operazione di Garbage Collection. Un profiler può ricevere una notifica all'inizio o alla fine di un'operazione di Garbage Collection e può fornire report sugli oggetti nell'heap gestito, inclusa un'identificazione degli oggetti in ogni generazione. Per altre informazioni, vedere Cenni preliminari sulla profilatura.
I profiler possono fornire informazioni complete. Tuttavia, i profiler complessi possono potenzialmente modificare il comportamento di un'applicazione.
Monitoraggio delle risorse del dominio applicazione
A partire da .NET Framework 4, il monitoraggio delle risorse del dominio dell'applicazione consente agli host di monitorare l'utilizzo di CPU e memoria da parte del dominio dell'applicazione. Per altre informazioni, vedere Monitoraggio delle risorse del dominio applicazione.
Risolvere i problemi di prestazioni
Il primo passaggio consiste nel determinare se il problema è in realtà causato dall'operazione di Garbage Collection. In questo caso, selezionare una delle voci nell'elenco seguente per risolvere il problema.
- Viene generata un'eccezione di memoria esaurita
- Il processo usa una quantità eccessiva di memoria
- Garbage Collector non recupera gli oggetti in modo abbastanza veloce
- L'heap gestito è troppo frammentato
- Le pause di Garbage Collection sono troppo prolungate
- La generazione 0 è troppo grande
- L'utilizzo della CPU durante un'operazione di Garbage Collection è eccessivo
Problema: viene generata un'eccezione di memoria esaurita
Esistono due casi legittimi per la generazione di un'eccezione OutOfMemoryException gestita:
Esaurimento della memoria virtuale.
Garbage Collector alloca memoria dal sistema in segmenti di dimensioni predeterminate. Se un'allocazione richiede un segmento aggiuntivo, ma non è più disponibile un blocco contiguo libero nello spazio di memoria virtuale del processo, l'allocazione per l'heap gestito non riesce.
Mancanza di memoria fisica sufficiente da allocare.
Se si determina che l'eccezione non è legittima, contattare il Supporto tecnico Microsoft avendo a portata di mano le informazioni seguenti:
- Stack con l'eccezione di memoria esaurita gestita.
- Dump di memoria completo.
- Dati che dimostrano che non si tratta di un'eccezione di memoria esaurita legittima, inclusi quelli che indicano che la memoria fisica o virtuale non è la causa del problema.
Problema: il processo usa una quantità eccessiva di memoria
Un presupposto comune è che l'uso della memoria visualizzato nella scheda Prestazioni di Gestione attività di Windows può indicare quando viene usata una quantità eccessiva di memoria. Tuttavia, queste informazioni visualizzate riguardano il set di lavoro e non l'utilizzo della memoria virtuale.
Se si determina che il problema è causato dall'heap gestito, è necessario misurare l'heap gestito nel tempo per stabilire gli eventuali modelli.
Se si determina che il problema non è causato dall'heap gestito, è necessario usare il debug nativo.
Problema: Garbage Collector non recupera gli oggetti in modo abbastanza veloce
Quando sembra che gli oggetti non vengano recuperati nel modo previsto per l'operazione di Garbage Collection, è necessario determinare se vi siano riferimenti sicuri a tali oggetti.
È possibile riscontrare questo problema anche se non è stata eseguita alcuna operazione di Garbage Collection per la generazione che contiene un oggetto inutilizzato, a indicare che il finalizzatore per tale oggetto inutilizzato non è stato eseguito. Ad esempio, ciò è possibile quando si esegue un'applicazione apartment a thread singolo (STA, Single-Threaded Apartment) e il thread che gestisce la coda del finalizzatore non può eseguire chiamate al suo interno.
Controlli delle prestazioni |
---|
Controllare i riferimenti agli oggetti. Determinare se è stato eseguito un finalizzatore. Determinare se sono presenti oggetti in attesa di essere finalizzati. |
Problema: l'heap gestito è troppo frammentato
Il livello di frammentazione viene calcolato come percentuale di spazio libero rispetto alla memoria totale allocata per la generazione. Per la generazione 2, un livello accettabile di frammentazione non è maggiore del 20%. Poiché la generazione 2 può assumere dimensioni molto grandi, il rapporto di frammentazione è più importante del valore assoluto.
La disponibilità di una quantità elevata di spazio libero nella generazione 0 non costituisce un problema, perché si tratta della generazione in cui vengono allocati nuovi oggetti.
La frammentazione si verifica sempre nell'heap oggetti grandi in quanto non viene compattato. Gli oggetti liberi adiacenti vengono naturalmente compressi in un unico spazio per soddisfare le richieste di allocazione di oggetti grandi.
La frammentazione può diventare un problema nella generazione 1 e nella generazione 2. Se in queste generazioni è disponibile una quantità elevata di spazio libero dopo un'operazione di Garbage Collection, potrebbe essere necessario modificare l'utilizzo degli oggetti di un'applicazione e provare a eseguire una nuova valutazione della durata degli oggetti a lungo termine.
Il blocco eccessivo di oggetti può aumentare la frammentazione. Se la frammentazione è elevata, potrebbe essere stato bloccato un numero eccessivo di oggetti.
Se la frammentazione della memoria virtuale impedisce a Garbage Collector di aggiungere segmenti, la causa può essere una delle seguenti:
Caricamento e scaricamento frequenti di molti assembly di piccole dimensioni.
Presenza di troppi riferimenti a oggetti COM durante l'interoperabilità con codice non gestito.
Creazione di oggetti temporanei di grandi dimensioni, che fa sì che l'heap grandi oggetti allochi e liberi frequentemente segmenti di heap.
Quando si ospita CLR, un'applicazione può richiedere che Garbage Collector ne mantenga i segmenti. In questo modo, viene ridotta la frequenza delle allocazioni di segmenti. A questo scopo, viene usato il contrassegno STARTUP_HOARD_GC_VM nell'Enumerazione STARTUP_FLAGS.
Controlli delle prestazioni |
---|
Determinare la quantità di spazio libero nell'heap gestito. Determinare il numero di oggetti bloccati. |
Se si ritiene che non sussista una causa legittima per la frammentazione, contattare il Supporto tecnico Microsoft.
Problema: le pause di Garbage Collection sono troppo prolungate
Poiché le operazioni di Garbage Collection avvengono in tempo reale "soft", un'applicazione deve essere in grado di tollerare alcune pause. Un criterio che determina l'esecuzione in tempo reale "soft" è che il 95% delle operazioni deve terminare in tempo.
Nelle operazioni di Garbage Collection simultanee, i thread gestiti possono essere eseguiti durante una raccolta, che significa che le pause sono minime.
Poiché le operazioni di Garbage Collection effimere (generazioni 0 e 1) durano solo pochi millisecondi, la riduzione delle pause non è in genere possibile. Tuttavia, è possibile ridurre le pause nelle raccolte di generazione 2 modificando il modello delle richieste di allocazione da parte di un'applicazione.
Un altro metodo, più accurato, consiste nell'usare gli eventi ETW di Garbage Collection. È possibile individuare gli intervalli di tempo per le raccolte aggiungendo le differenze dei timestamp per una sequenza di eventi. L'intera sequenza di raccolta include la sospensione del motore di esecuzione, l'operazione di Garbage Collection stessa e la ripresa del motore di esecuzione.
È possibile usare le notifiche di Garbage Collection per determinare se in un server sta per essere eseguita una raccolta di generazione 2 e se le richieste di reindirizzamento a un altro server potrebbero risolvere eventuali problemi relativi alle pause.
Controlli delle prestazioni |
---|
Determinare la durata di un'operazione di Garbage Collection. Determinare la causa di un'operazione di Garbage Collection. |
Problema: la generazione 0 ha dimensioni eccessive
È probabile che la generazione 0 abbia un numero maggiore di oggetti in un sistema a 64 bit, in particolare se si usa un'operazione di Garbage Collection per server invece che per workstation. Ciò è dovuto al fatto che la soglia per attivare un'operazione di Garbage Collection di generazione 0 è maggiore in questi ambienti e le raccolte di generazione 0 possono essere molto più grandi. Le prestazioni sono migliori quando un'applicazione alloca molta memoria prima dell'attivazione di un'operazione di Garbage Collection.
Problema: l'utilizzo della CPU durante un'operazione di Garbage Collection è eccessivo
L'utilizzo della CPU durante un'operazione di Garbage Collection è elevato. Se il periodo di tempo del processo è significativo in un'operazione di Garbage Collection, il numero di raccolte è troppo frequente oppure la raccolta dura troppo a lungo. Una frequenza di allocazione di oggetti maggiore nell'heap gestito fa sì che le operazioni di Garbage Collection avvengano più spesso. La riduzione della frequenza di allocazione riduce anche la frequenza delle operazioni di Garbage Collection.
È possibile monitorare le frequenze di allocazione usando il contatore delle prestazioni Allocated Bytes/second
. Per altre informazioni, vedere Contatori delle prestazioni in .NET.
La durata di una raccolta è principalmente un fattore del numero di oggetti rimanenti in seguito all'allocazione. Garbage Collector deve usare una quantità elevata di memoria se restano molti oggetti da raccogliere. L'attività di compattazione degli oggetti rimanenti richiede molto tempo. Per determinare quanti oggetti sono stati gestiti durante una raccolta, impostare un punto di interruzione nel debugger alla fine di un'operazione di Garbage Collection per una generazione specificata.
Linee guida per la risoluzione dei problemi
Questa sezione contiene linee guida da tenere presenti quando si inizia a esaminare i problemi.
Garbage Collection per workstation o per server
Determinare se il tipo di Garbage Collection usato è quello corretto. Se l'applicazione usa più thread e istanze di oggetto, usare Garbage Collection per server anziché per workstation. Un'operazione di Garbage Collection per server viene eseguita su più thread, mentre una per workstation richiede l'esecuzione di più istanze di un'applicazione nei rispettivi thread di Garbage Collection e che queste competano per il tempo di CPU.
Un'applicazione associata a un carico ridotto e che esegue raramente attività in background, ad esempio un servizio, può usare un'operazione di Garbage Collection per workstation con le operazioni di Garbage Collection simultanee disattivate.
Quando misurare le dimensioni dell'heap gestito
Se non si usa un profiler, è necessario definire un modello di misurazione coerente per diagnosticare con efficacia i problemi di prestazioni. Per definire una pianificazione, considerare i punti seguenti:
- Se si esegue una misurazione dopo un'operazione di Garbage Collection di generazione 2, l'intero heap gestito sarò libero da oggetti inutilizzati.
- Se si esegue una misurazione immediatamente dopo un'operazione di Garbage Collection di generazione 0, gli oggetti nelle generazioni 1 e 2 non verranno ancora raccolti.
- Se si esegue una misurazione immediatamente prima di un'operazione di Garbage Collection, è necessario misurare tutta l'allocazione possibile prima dell'avvio dell'operazione di Garbage Collection.
- La misurazione durante un'operazione di Garbage Collection è problematica, perché le strutture di dati di Garbage Collector non sono in uno stato valido per l'attraversamento e potrebbero non essere in grado di fornire i risultati completi. Questo si verifica per motivi strutturali.
- Quando si usa un'operazione di Garbage Collection per workstation con un'operazione di Garbage Collection simultanea, gli oggetti recuperati non vengono compattati e di conseguenza le dimensioni dell'heap possono essere uguali o maggiori (la frammentazione può farle apparire maggiori).
- Un'operazione di Garbage Collection simultanea nella generazione 2 viene ritardata quando il carico di memoria fisica è troppo elevato.
La procedura seguente descrive come impostare un punto di interruzione per misurare l'heap gestito.
Per impostare un punto di interruzione alla fine dell'operazione di Garbage Collection
In WinDbg con l'estensione del debugger SOS caricata, immettere il comando seguente:
bp mscorwks!WKS::GCHeap::RestartEE "j (dwo(mscorwks!WKS::GCHeap::GcCondemnedGeneration)==2) 'kb';'g'"
Impostare
GcCondemnedGeneration
sulla generazione desiderata. Questo comando richiede simboli privati.Il comando forza un'interruzione se
RestartEE
viene eseguito dopo il recupero di oggetti di generazione 2 per l'operazione di Garbage Collection.In un'operazione di Garbage Collection per server, solo un thread chiama
RestartEE
, in modo che il punto di interruzione si verifica solo una volta durante un'operazione di Garbage Collection di generazione 2.
Procedure di controllo delle prestazioni
Questa sezione descrive le procedure per isolare la causa del problema di prestazioni:
- Determinare se il problema è causato da Garbage Collection
- Determinare se l'eccezione di memoria esaurita è gestita.
- Determinare la quantità di memoria virtuale che è possibile riservare.
- Determinare se è disponibile memoria fisica sufficiente.
- Determinare la quantità di memoria di cui l'heap gestito esegue il commit.
- Determinare la quantità di memoria riservata dall'heap gestito.
- Determinare gli oggetti di grandi dimensioni nella generazione 2.
- Determinare i riferimenti agli oggetti.
- Determinare se è stato eseguito un finalizzatore.
- Determinare se sono presenti oggetti in attesa di essere finalizzati.
- Determinare la quantità di spazio libero nell'heap gestito.
- Determinare il numero di oggetti bloccati.
- Determinare la durata di un'operazione di Garbage Collection.
- Determinare che cosa ha attivato un'operazione di Garbage Collection.
- Determinare se l'utilizzo elevato della CPU è causato da Garbage Collection.
Per determinare se il problema è causato da Garbage Collection
Esaminare i due contatori delle prestazioni della memoria seguenti:
Percentuale tempo in GC. Visualizza la percentuale di tempo trascorso, impiegato per l'esecuzione di un'operazione di Garbage Collection dopo l'ultimo ciclo di Garbage Collection. Usare questo contatore per determinare se Garbage Collector sta impiegando troppo tempo per rendere disponibile lo spazio dell'heap gestito. Se il tempo impiegato nell'operazione di Garbage Collection è relativamente ridotto, ciò può indicare un problema di risorse all'esterno dell'heap gestito. Questo contatore potrebbe non essere accurato se sono interessate operazioni di Garbage Collection simultanee o in background.
N. totale di byte con commit. Visualizza la quantità di memoria virtuale di cui Garbage Collector ha attualmente eseguito il commit. Usare questo contatore per determinare se la memoria usata da Garbage Collector è una parte eccessiva della memoria usata dall'applicazione.
La maggior parte dei contatori delle prestazioni della memoria viene aggiornata alla fine di ogni operazione di Garbage Collection. Di conseguenza, i contatori potrebbero non riflettere le attuali condizioni su cui si vuole ottenere informazioni.
Per determinare se l'eccezione di memoria esaurita è gestita
Nel debugger WinDbg o di Visual Studio con l'estensione del debugger SOS caricata, digitare il comando di stampa dell'eccezione (
pe
):!pe
Se l'eccezione è gestita, OutOfMemoryException viene visualizzato come tipo di eccezione, come mostrato nell'esempio seguente.
Exception object: 39594518 Exception type: System.OutOfMemoryException Message: <none> InnerException: <none> StackTrace (generated):
Se l'output non specifica un'eccezione, è necessario determinare il thread da cui proviene l'eccezione di memoria esaurita. Immettere il comando seguente nel debugger per mostrare tutti i thread con i rispettivi stack di chiamate:
~\*kb
Il thread con lo stack che include le chiamate dell'eccezione è indicato dall'argomento
RaiseTheException
. Si tratta dell'oggetto eccezione gestita.28adfb44 7923918f 5b61f2b4 00000000 5b61f2b4 mscorwks!RaiseTheException+0xa0
È possibile usare il comando seguente per eseguire il dump delle eccezioni annidate:
!pe -nested
Se non si trova alcuna eccezione, l'eccezione di memoria esaurita proviene da codice non gestito.
Per determinare la quantità di memoria virtuale che è possibile riservare
In WinDbg con l'estensione del debugger SOS caricata, immettere il comando seguente per ottenere l'area libera più grande:
!address -summary
L'area libera più grande viene visualizzata come indicato nell'output seguente.
Largest free region: Base 54000000 - Size 0003A980
In questo esempio la dimensione dell'area libera più grande è circa 24000 KB (3A980 in formato esadecimale). Questa area è molto minore rispetto alle dimensioni richieste da Garbage Collector per un segmento.
-o-
Usare il comando
vmstat
:!vmstat
L'area libera più grande corrisponde al valore maggiore nella colonna MAXIMUM, come indicato nell'output seguente.
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
Per determinare se è disponibile memoria fisica sufficiente
Avviare Gestione risorse di Windows.
Nella scheda
Performance
esaminare il valore di cui è stato eseguito il commit. (In Windows 7 esaminareCommit (KB)
inSystem group
.)Se
Total
è vicino aLimit
, la memoria fisica è insufficiente.
Per determinare la quantità di memoria di cui l'heap gestito esegue il commit
Usare il contatore delle prestazioni della memoria
# Total committed bytes
per ottenere il numero di byte di cui l'heap gestito sta eseguendo il commit. Garbage Collector esegue il commit dei blocchi in un segmento nel modo necessario e non di tutti allo stesso tempo.Nota
Non usare il contatore delle prestazioni
# Bytes in all Heaps
, perché non rappresenta l'effettivo utilizzo della memoria da parte dell'heap gestito. La dimensione di una generazione è inclusa in questo valore e corrisponde i n realtà alla dimensione della soglia, ovvero la dimensione che provoca un'operazione di Garbage Collection se la generazione include molti oggetti. Di conseguenza, questo valore è in genere uguale a zero.
Per determinare la quantità di memoria riservata dall'heap gestito
Usare il contatore delle prestazioni della memoria
# Total reserved bytes
.Garbage Collector riserva memoria in segmenti ed è possibile determinare il punto di inizio di un segmento usando il comando
eeheap
.Importante
Benché sia possibile determinare la quantità di memoria allocata da Garbage Collector per ogni segmento, le dimensioni dei segmenti sono specifiche dell'implementazione e soggette a modifiche, tra cui aggiornamenti periodici, in qualsiasi momento. L'applicazione non deve dare per scontata o dipendere da una particolare dimensione del segmento, né provare a configurare la quantità di memoria disponibile per le allocazioni di segmenti.
Nel debugger WinDbg o di Visual Studio con l'estensione del debugger SOS caricata, immettere il comando seguente:
!eeheap -gc
Il risultato è il seguente.
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)
Gli indirizzi indicati da "segment" sono gli indirizzi iniziali dei segmenti.
Per determinare gli oggetti di grandi dimensioni nella generazione 2
Nel debugger WinDbg o di Visual Studio con l'estensione del debugger SOS caricata, immettere il comando seguente:
!dumpheap –stat
Se l'heap gestito è di grandi dimensioni,
dumpheap
può richiedere alcuni minuti.È possibile iniziare l'analisi dalle ultime righe dell'output, perché elencano gli oggetti che usano la maggior parte dello spazio. Ad esempio:
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
L'ultimo oggetto elencato è una stringa e occupa la maggior parte dello spazio. È possibile esaminare l'applicazione per determinare come ottimizzare gli oggetti stringa. Per visualizzare stringhe comprese tra 150 e 200 byte, immettere quanto segue:
!dumpheap -type System.String -min 150 -max 200
Di seguito viene riportato un esempio dei risultati.
Address MT Size Gen 1875d2c0 790fa3e0 152 2 System.String HighlightNullStyle_Blotter_PendingOrder-11_Blotter_PendingOrder-11 …
L'uso di un numero intero invece di una stringa per un ID può rivelarsi più efficiente. Se la stessa stringa è ripetuta migliaia di volte, provare ad applicare l'inserimento di stringhe. Per altre informazioni sull'inserimento di stringhe, vedere l'argomento di riferimento per il metodo String.Intern.
Per determinare i riferimenti agli oggetti
In WinDbg con l'estensione del debugger SOS caricata, immettere il comando seguente per elencare i riferimenti agli oggetti:
!gcroot
-o-
Per determinare i riferimenti per un oggetto specifico, includere l'indirizzo:
!gcroot 1c37b2ac
Le radici trovate negli stack possono essere falsi positivi. Per altre informazioni, usare il comando
!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
Il comando
gcroot
può richiedere molto tempo. Qualsiasi oggetto non recuperato dall'operazione di Garbage Collection è un oggetto attivo. Questo significa che una radice mantiene direttamente o indirettamente l'oggetto, motivo per cuigcroot
deve restituire le informazioni sul percorso dell'oggetto. È consigliabile esaminare i grafici restituiti e determinare perché viene ancora fatto riferimento a questi oggetti.
Per determinare se è stato eseguito un finalizzatore
Eseguire un programma di test contenente il codice seguente:
GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect();
Se il test risolve il problema, significa che Garbage Collector non ha recuperato gli oggetti perché il finalizzatore per tali oggetti è stato sospeso. Il metodo GC.WaitForPendingFinalizers permette ai finalizzatori di completare le rispettive attività e corregge il problema.
Per determinare se sono presenti oggetti in attesa di essere finalizzati
Nel debugger WinDbg o di Visual Studio con l'estensione del debugger SOS caricata, immettere il comando seguente:
!finalizequeue
Osservare il numero di oggetti pronti per la finalizzazione. Se il numero è elevato, è necessario esaminare il motivo per cui i finalizzatori non possono continuare del tutto o in modo abbastanza veloce.
Per ottenere un output dei thread, immettere il comando seguente:
!threads -special
Questo comando genera un output simile al seguente.
OSID Special thread type 2 cd0 DbgHelper 3 c18 Finalizer 4 df0 GC SuspendEE
Il thread del finalizzatore indica quale finalizzatore, se presente, è attualmente in esecuzione. Quando un thread del finalizzatore non esegue alcun finalizzatore, è in attesa di un evento che indichi di eseguire il lavoro. Nella maggior parte dei casi il thread del finalizzatore verrà visualizzato in questo stato, perché viene eseguito in THREAD_HIGHEST_PRIORITY e si suppone che completi l'esecuzione dei finalizzatori, se presenti, molto rapidamente.
Per determinare la quantità di spazio libero nell'heap gestito
Nel debugger WinDbg o di Visual Studio con l'estensione del debugger SOS caricata, immettere il comando seguente:
!dumpheap -type Free -stat
Questo comando visualizza le dimensioni totali di tutti gli oggetti liberi nell'heap gestito, come mostrato nell'esempio seguente.
total 230 objects Statistics: MT Count TotalSize Class Name 00152b18 230 40958584 Free Total 230 objects
Per determinare lo spazio libero nella generazione 0, immettere il comando seguente per informazioni sull'utilizzo della memoria da parte della generazione:
!eeheap -gc
Questo comando genera un output simile al seguente. L'ultima riga mostra il segmento effimero.
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)
Calcolare lo spazio usato dalla generazione 0:
? 49e05d04-0x49521f8c
Il risultato è il seguente. La generazione 0 è di circa 9 MB.
Evaluate expression: 9321848 = 008e3d78
Il comando seguente esegue il dump dello spazio libero all'interno dell'intervallo della generazione 0:
!dumpheap -type Free -stat 0x49521f8c 49e05d04
Il risultato è il seguente.
------------------------------ 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
Questo output mostra che la parte di generazione 0 dell'heap usa 9 MB di spazio per gli oggetti e ha 7 MB di spazio libero. Questa analisi mostra in quale misura la generazione 0 contribuisce alla frammentazione. Questa quantità di utilizzo dell'heap deve essere sottratta dalla quantità locale come causa di frammentazione da parte di oggetti a lungo termine.
Per determinare il numero di oggetti bloccati
Nel debugger WinDbg o di Visual Studio con l'estensione del debugger SOS caricata, immettere il comando seguente:
!gchandles
Le statistiche visualizzate includono il numero di handle bloccati, come indicato nell'esempio seguente.
GC Handle Statistics: Strong Handles: 29 Pinned Handles: 10
Per determinare il periodo di tempo in un'operazione di Garbage Collection
Esaminare il contatore delle prestazioni della memoria
% Time in GC
.Il valore viene calcolato usando un intervallo di tempo di esempio. Poiché i contatori vengono aggiornati alla fine di ogni operazione di Garbage Collection, l'esempio corrente avrà lo stesso valore di quello precedente se durante l'intervallo non è stata eseguita alcuna raccolta.
La durata della raccolta viene ottenuta moltiplicando l'intervallo di esempio per il valore percentuale.
I dati seguenti mostrano quattro intervalli di campionamento di due secondi, per uno studio di otto secondi. Le colonne
Gen0
,Gen1
eGen2
mostrano il numero totale di Garbage Collection completati entro la fine dell'intervallo per la generazione.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
Queste informazioni non indicano quando è avvenuta l'operazione di Garbage Collection, ma è possibile determinare il numero di operazioni di Garbage Collection eseguite in un intervallo di tempo specifico. Presupponendo il caso peggiore, la decima operazione di Garbage Collection di generazione 0 è terminata all'inizio del secondo intervallo, mentre l'undicesima operazione di Garbage Collection di generazione 0 è terminata alla fine del terzo intervallo. Poiché l'intervallo di tempo tra la fine della decima e la fine dell'undicesima operazione di Garbage Collection è circa 2 secondi e il contatore delle prestazioni indica 3%, la durata dell'undicesima operazione di Garbage Collection di generazione 0 è pari a (2 secondi * 3% = 60 ms).
Nell'esempio seguente sono presenti cinque intervalli.
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
La seconda operazione di Garbage Collection di generazione 2 è iniziata durante il quarto intervallo ed è terminata durante il quinto intervallo. Presupponendo il caso peggiore, l'ultima operazione di Garbage Collection è avvenuta per una raccolta di generazione 0 terminata all'inizio del terzo intervallo, mentre l'operazione di Garbage Collection di generazione 2 è terminata alla fine del quinto intervallo. Di conseguenza, l'intervallo di tempo tra la fine dell'operazione di Garbage Collection di generazione 0 e la fine dell'operazione di Garbage Collection di generazione 2 è 4 secondi. Poiché il contatore
% Time in GC
indica 20%, la quantità massima di tempo che l'operazione di Garbage Collection di generazione 2 avrebbe potuto impiegare è (4 secondi * 20% = 800 ms).In alternativa, è possibile determinare la lunghezza di un'operazione di Garbage Collection usando gli eventi ETW di Garbage Collection e analizzando le informazioni per stabilire la durata dell'operazione di Garbage Collection.
Ad esempio, i dati seguenti mostrano una sequenza di eventi verificatasi durante un'operazione di Garbage Collection non simultanea.
Timestamp Event name 513052 GCSuspendEEBegin_V1 513078 GCSuspendEEEnd 513090 GCStart_V1 517890 GCEnd_V1 517894 GCHeapStats 517897 GCRestartEEBegin 517918 GCRestartEEEnd
La sospensione del thread gestito ha richiesto 26 us (
GCSuspendEEEnd
-GCSuspendEEBegin_V1
).L'effettiva operazione di Garbage Collection ha richiesto 4,8 ms (
GCEnd_V1
-GCStart_V1
).La ripresa dei thread gestiti ha richiesto 21 us (
GCRestartEEEnd
-GCRestartEEBegin
).L'output seguente offre un esempio di un'operazione di Garbage Collection in background e include il processo, il thread e i campi evento. Non tutti i dati vengono visualizzati.
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'evento
GCStart_V1
in corrispondenza di 42504816 indica che si tratta di un'operazione di Garbage Collection in background, perché l'ultimo campo è1
. Questa diventa l'operazione di Garbage Collection n. 102019.L'evento
GCStart
si verifica perché è necessaria un'operazione di Garbage Collection effimera prima di avviare un'operazione di Garbage Collection in background. Questa diventa l'operazione di Garbage Collection n. 102020.Al 42514170, il processo di Garbage Collection n. 102020 termina. I thread gestiti vengono riavviati a questo punto. Questa operazione viene completata nel thread 4372, che ha attivato l'operazione di Garbage Collection in background.
Nel thread 4744 si verifica una sospensione. Si tratta dell'unica volta in cui l'operazione di Garbage Collection in background deve sospendere i thread gestiti. Questa durata è di circa 99 ms ((63784407-63685394)/1000).
L'evento
GCEnd
per l'attività di Garbage Collection in background si trova in corrispondenza di 89931423. Di conseguenza, l'operazione di Garbage Collection è durata circa 47 secondi ((89931423-42504816)/1000).Durante l'esecuzione dei thread gestiti, è possibile visualizzare un numero qualsiasi di operazioni di Garbage Collection effimere in esecuzione.
Per determinare che cosa ha attivato un'operazione di Garbage Collection
Nel debugger WinDbg o di Visual Studio con l'estensione del debugger SOS caricata, immettere il comando seguente per mostrare tutti i thread con i rispettivi stack di chiamate:
~*kb
Questo comando genera un output simile al seguente.
0012f3b0 79ff0bf8 mscorwks!WKS::GCHeap::GarbageCollect 0012f454 30002894 mscorwks!GCInterface::CollectGeneration+0xa4 0012f490 79fa22bd fragment_ni!request.Main(System.String[])+0x48
Se l'operazione di Garbage Collection è stata provocata da una notifica di memoria insufficiente dal sistema operativo, lo stack di chiamate è simile, ad eccezione del fatto che il thread è il thread del finalizzatore. Il thread del finalizzatore ottiene una notifica di memoria insufficiente asincrona e provoca l'operazione di Garbage Collection.
Se l'operazione di Garbage Collection è stata causata dall'allocazione di memoria, lo stack viene visualizzato in questo modo:
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 helper JIT (
JIT_New*
) chiama infineGCHeap::GarbageCollectGeneration
. Se si determina che le operazioni di Garbage Collection di generazione 2 sono causate da allocazioni, è necessario definire gli oggetti raccolti da tali operazioni e come evitarli. Ovvero, si vuole determinare la differenza tra l'inizio e la fine di un'operazione di Garbage Collection di generazione 2, nonché gli oggetti che hanno provocato la raccolta di generazione 2.Ad esempio, immettere il comando seguente nel debugger per mostrare l'inizio di una raccolta di generazione 2:
!dumpheap –stat
Output di esempio (abbreviato per mostrare gli oggetti che usano la maggior parte dello spazio):
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
Ripetere il comando alla fine della generazione 2:
!dumpheap –stat
Output di esempio (abbreviato per mostrare gli oggetti che usano la maggior parte dello spazio):
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
Gli oggetti
double[]
sono scomparsi dalla fine dell'output, a indicare che sono stati raccolti. Questi oggetti corrispondono a circa 70 MB. Gli oggetti rimanenti non sono cambiati molto. Di conseguenza, questi oggettidouble[]
sono il motivo per cui si è verificata un'operazione di Garbage Collection di generazione 2 . Il passaggio successivo consiste nel determinare il motivo per cui gli oggettidouble[]
sono ancora presenti e perché sono diventati inutilizzati. È possibile chiedere allo sviluppatore del codice da dove provengono questi oggetti oppure è possibile usare il comandogcroot
.
Per determinare se l'utilizzo elevato della CPU è causato da Garbage Collection
Correlare il valore del contatore delle prestazioni della memoria
% Time in GC
alla durata del processo.Se il valore
% Time in GC
mostra la presenza di picchi contemporaneamente alla durata del processo, l'operazione di Garbage Collection è la causa dell'elevato utilizzo della CPU. In caso contrario, profilare l'applicazione per individuare il punto in cui si verifica un utilizzo elevato.