Condividi tramite


Garbage Collection e prestazioni

In questo argomento vengono descritti i problemi correlati a Garbage Collection e utilizzo della memoria. Vengono analizzati i problemi relativi all'heap gestito e viene illustrato come ridurre al minimo l'effetto di un'operazione di Garbage Collection sulle applicazioni. Per ogni problema sono disponibili collegamenti a procedure che è possibile utilizzare per approfondire l'analisi.

In questo argomento sono incluse le sezioni seguenti:

  • Strumenti di analisi delle prestazioni

  • Risoluzione dei problemi di prestazioni

  • Linee guida per la risoluzione dei problemi

  • Procedure di controllo delle prestazioni

Strumenti di analisi delle prestazioni

Nelle sezioni seguenti vengono descritti gli strumenti disponibili per analizzare i problemi di utilizzo della memoria e Garbage Collection. Le procedure illustrate più avanti in questo argomento fanno riferimento a questi strumenti.

Contatori delle prestazioni di memoria

È possibile utilizzare 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 di prestazioni di memoria, fornisce informazioni sul Garbage Collector.

Debugger con SOS

È possibile utilizzare il debugger di Windows (WinDbg) o il debugger di Visual Studio con SOS.dll (estensione del debugger SOS) per esaminare gli oggetti nell'heap gestito.

Per installare WinDbg, installare gli strumenti di debug per Windows (Debugging Tools for Windows) dal sito Web WDK and Developer Tools. Per informazioni sull'utilizzo dell'estensione del debugger SOS, vedere How to: Use SOS.

Eventi ETW di Garbage Collection

Event tracing for Windows (ETW) è un sistema di traccia che completa il supporto per la profilatura e il debug fornito da .NET Framework. A partire da .NET Framework versione 4, gli eventi ETW di Garbage Collection consentono di acquisire informazioni utili per l'analisi dell'heap gestito da un punto di vista statistico. L'evento GCStart_V1, generato quando sta per essere eseguita un'operazione di Garbage Collection, fornisce ad esempio le informazioni seguenti:

  • Generazione di oggetti di cui è in corso la raccolta.

  • Causa dell'attivazione dell'operazione di Garbage Collection.

  • Tipo di operazione di Garbage Collection (simultanea o non simultanea).

La registrazione eventi ETW è efficiente e non maschera eventuali problemi di prestazioni associati all'operazione di Garbage Collection. Un processo può fornire i propri eventi in combinazione agli eventi ETW. Una volta registrati, sia gli eventi dell'applicazione che gli eventi di Garbage Collection possono essere correlati per determinare come e quando si verificano i problemi dell'heap. Un'applicazione server può ad esempio fornire eventi all'inizio e alla fine di una richiesta del client.

API di analisi

Le interfacce di profilatura CLR (Common Language Runtime) forniscono informazioni dettagliate sugli oggetti interessati dall'operazione di Garbage Collection. Un profiler può ricevere una notifica all'avvio e al termine di un'operazione di Garbage Collection. Può fornire rapporti sugli oggetti nell'heap gestito, inclusa un'identificazione degli oggetti in ogni generazione. Per ulteriori informazioni, vedere Object Tracking in the Profiling API.

I profiler possono fornire informazioni complete. I profiler complessi, tuttavia, possono 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 (ARM) consente agli host di monitorare l'utilizzo di CPU e memoria in base al dominio dell'applicazione. Per ulteriori informazioni, vedere Monitoraggio delle risorse del dominio applicazione.

Torna all'inizio

Risoluzione dei problemi di prestazioni

Il primo passaggio consiste nel determinare se il problema è effettivamente di Garbage Collection. In caso affermativo, selezionare la voce appropriata nell'elenco seguente per risolvere il problema.

  • Viene generata un'eccezione di memoria insufficiente

  • Viene utilizzata troppa memoria dal processo

  • Gli oggetti non vengono recuperati abbastanza velocemente dal Garbage Collector

  • L'heap gestito è troppo frammentato

  • Le pause di Garbage Collection sono troppo lunghe

  • La generazione 0 è troppo grande

  • L'utilizzo della CPU durante un'operazione di Garbage Collection è troppo elevato

Problema: viene generata un'eccezione di memoria insufficiente

Vi sono due casi legittimi per la generazione di un evento OutOfMemoryException gestito:

  • Esaurimento della memoria virtuale.

    Tramite il Garbage Collector viene allocata memoria dal sistema in segmenti di dimensioni predeterminate. Se un'allocazione richiede un segmento aggiuntivo, ma nello spazio di memoria virtuale del processo non è rimasto un blocco libero contiguo, si verifica un errore nell'allocazione per l'heap gestito.

  • Memoria fisica insufficiente per l'allocazione.

Controlli delle prestazioni

Determinare se l'eccezione di memoria insufficiente è gestita.

Determinare la quantità di memoria virtuale che è possibile riservare.

Determinare se la memoria fisica disponibile è sufficiente.

Se si determina che l'eccezione non è legittima, contattare il Servizio Supporto Tecnico Clienti Microsoft e fornire le informazioni seguenti:

  • Stack con l'eccezione di memoria insufficiente gestita.

  • Dump di memoria completo.

  • Dati che dimostrano che non si tratta di un'eccezione di memoria insufficiente legittima, inclusi dati che indicano che la memoria virtuale o fisica non rappresenta un problema.

Problema: viene utilizzata troppa memoria dal processo

In genere, gli utenti ritengono che l'indicazione relativa all'utilizzo della memoria nella scheda Prestazioni di Gestione attività Windows indichi quando viene utilizzata troppa memoria. Tale indicazione, tuttavia, riguarda il working set e non fornisce informazioni sull'utilizzo di memoria virtuale.

Se si determina che il problema è causato dall'heap gestito, è necessario misurare l'heap gestito nel tempo per determinare eventuali modelli.

Se si determina che il problema non è causato dall'heap gestito, è necessario utilizzare il debug nativo.

Controlli delle prestazioni

Determinare la quantità di memoria virtuale che è possibile riservare.

Determinare la quantità di memoria di cui viene eseguito il commit dall'heap gestito.

Determinare la quantità di memoria riservata dall'heap gestito.

Determinare gli oggetti di grandi dimensioni nella generazione 2.

Determinare i riferimenti agli oggetti.

Problema: gli oggetti non vengono recuperati abbastanza velocemente dal Garbage Collector

Quando sembra che gli oggetti non vengano recuperati come previsto per un'operazione di Garbage Collection, è necessario determinare se vi sono riferimenti forti a tali oggetti.

Questo problema può verificarsi anche se non è stata effettuata un'operazione di Garbage Collection per la generazione che contiene un oggetto inutilizzato, a indicare che il finalizzatore per l'oggetto non utilizzato non è stato eseguito. Questo può verificarsi, ad esempio, quando si esegue un'applicazione STA (Single-Threaded Apartment, apartment a thread singolo) e il thread che serve la coda del finalizzatore non può eseguire la chiamata.

Controlli delle prestazioni

Controllare i riferimenti agli oggetti.

Determinare se è stato eseguito un finalizzatore.

Determinare se vi sono oggetti in attesa di finalizzazione.

Problema: l'heap gestito è troppo frammentato

Il livello di frammentazione viene calcolato come il rapporto tra spazio disponibile e memoria totale allocata per la generazione. Per la generazione 2, un livello accettabile di frammentazione non deve essere superiore al 20%. Poiché la generazione 2 può diventare molto grande, il rapporto di frammentazione è più importante rispetto al valore assoluto.

La presenza di molto spazio disponibile nella generazione 0 non è un problema, in quanto si tratta della generazione in cui vengono allocati i nuovi oggetti.

La frammentazione si verifica sempre nell'heap di oggetti grandi, poiché non vi è compattazione. Gli oggetti liberi adiacenti vengono naturalmente compressi in un singolo spazio per soddisfare le richieste di allocazione di oggetti di grandi dimensioni.

La frammentazione può diventare un problema nella generazione 1 e nella generazione 2. Se in queste generazioni è presente una grande quantità di spazio disponibile dopo un'operazione di Garbage Collection, potrebbe essere necessario modificare l'utilizzo degli oggetti di un'applicazione e prendere in considerazione la rivalutazione della durata degli oggetti a lungo termine.

Il blocco eccessivo degli oggetti può causare un aumento della frammentazione. Se la frammentazione è elevata, possono venire bloccati troppi oggetti.

Se la frammentazione della memoria virtuale impedisce al Garbage Collector di aggiungere segmenti, la causa potrebbe essere una delle seguenti:

  • Caricamento e scaricamento frequenti di numerosi assembly di piccole dimensioni.

  • Presenza di troppi riferimenti agli oggetti COM durante l'interazione con il codice non gestito.

  • Creazione di oggetti temporanei di grandi dimensioni, a causa della quale i segmenti dell'heap vengono allocati e liberati frequentemente dall'heap di oggetti grandi.

    Quando ospita CLR, un'applicazione può richiedere che il Garbage Collector mantenga i suoi segmenti. Questo consente una riduzione della frequenza delle allocazioni di segmenti. A tale scopo, è necessario utilizzare il flag STARTUP_HOARD_GC_VM nell'STARTUP_FLAGS Enumeration.

Controlli delle prestazioni

Determinare la quantità di spazio disponibile nell'heap gestito.

Determinare il numero di oggetti bloccati.

Se si ritiene che non vi sia una causa legittima per la frammentazione, contattare il Servizio Supporto Tecnico Clienti Microsoft.

Problema: le pause di Garbage Collection sono troppo lunghe

Le operazioni di Garbage Collection avvengono in tempo reale flessibile (soft real time), pertanto un'applicazione deve essere in grado di tollerare alcune pause. Un criterio per il tempo reale flessibile (soft real time) prevede che il 95% delle operazioni debba finire entro il tempo previsto.

Nelle operazioni di Garbage Collection simultanee è concessa l'esecuzione di thread gestiti durante una raccolta, pertanto le pause sono minime.

Le operazioni di Garbage Collection temporanee (generazioni 0 e 1) durano solo alcuni millisecondi, pertanto in genere non è possibile ridurre le pause. È tuttavia possibile ridurre le pause nelle operazioni di raccolta di generazione 2, modificando il modello delle richieste di allocazione da parte di un'applicazione.

Un altro metodo più accurato consiste nell'utilizzo degli eventi ETW di Garbage Collection. È possibile calcolare i tempi per le operazioni di raccolta sommando le differenze dei timestamp per una sequenza di eventi. L'intera sequenza di raccolta comprende la sospensione del motore di esecuzione, l'operazione stessa di Garbage Collection e la ripresa del motore di esecuzione.

È possibile utilizzare Notifiche di Garbage Collection per determinare se nel server sta per essere eseguita una raccolta di generazione 2 e se il reindirizzamento delle richieste a un altro server potrebbe aiutare a risolvere eventuali problemi con le 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 è troppo grande

La generazione 0 include con ogni probabilità un numero maggiore di oggetti in un sistema a 64 bit, in particolare quando si utilizza un'operazione di Garbage Collection per il server anziché per la workstation. Questo si verifica in quanto la soglia per l'attivazione di un'operazione di Garbage Collection di generazione 0 è superiore in questi ambienti e le raccolte di generazione 0 possono diventare molto più grandi. Le prestazioni migliorano quando tramite un'applicazione viene allocata una quantità maggiore di memoria prima dell'attivazione di un'operazione di Garbage Collection.

Problema: l'utilizzo della CPU durante un'operazione di Garbage Collection è troppo elevato

L'utilizzo della CPU è elevato durante un'operazione di Garbage Collection. Se una quantità significativa di tempo dei processi viene utilizzata per un'operazione di Garbage Collection, il numero di operazioni di raccolta è troppo frequente o la durata della raccolta è eccessiva. Una maggiore frequenza di allocazione degli oggetti nell'heap gestito comporta una maggiore frequenza delle operazioni di Garbage Collection. La diminuzione della frequenza di allocazione comporta la riduzione della frequenza delle operazioni di Garbage Collection.

È possibile monitorare le frequenze di allocazione utilizzando il contatore delle prestazioni Allocated Bytes/second. Per ulteriori informazioni, vedere Contatori di prestazioni di memoria.

La durata di un'operazione di raccolta è principalmente un fattore del numero di oggetti che rimangono dopo l'allocazione. Il Garbage Collector deve attraversare un'elevata quantità di memoria se rimangono numerosi oggetti da raccogliere. Il lavoro per compattare gli oggetti rimasti richiede tempo. Per determinare la quantità di oggetti gestiti durante una raccolta, impostare un punto di interruzione alla fine di un'operazione di Garbage Collection per una generazione specifica.

Controlli delle prestazioni

Determinare se l'elevato utilizzo della CPU è causato da un'operazione di Garbage Collection.

Impostare un punto di interruzione alla fine di un'operazione di Garbage Collection.

Torna all'inizio

Linee guida per la risoluzione dei problemi

In questa sezione vengono descritte le linee guida da tenere in considerazione quando si iniziano le analisi.

Garbage Collection per workstation o server

Determinare se si sta utilizzando il tipo di Garbage Collection corretto. Se nell'applicazione vengono utilizzati più thread e istanze di oggetto, utilizzare un'operazione di Garbage Collection per il server anziché per la workstation. Un'operazione di Garbage Collection per il server agisce su più thread, mentre un'operazione di Garbage Collection per la workstation richiede che più istanze di un'applicazione eseguano i propri thread di Garbage Collection e competano per il tempo di CPU.

Per un'applicazione con un carico basso e da cui vengono eseguite raramente attività in background, ad esempio un servizio, può venire utilizzata un'operazione di Garbage Collection per workstation con la modalità di Garbage Collection simultanea disabilitata.

Casi in cui misurare la dimensione dell'heap gestito

A meno che non si utilizzi un profiler, è necessario stabilire un modello di misurazione coerente per diagnosticare in modo efficace i problemi di prestazioni. Prendere in considerazione i punti seguenti per stabilire una pianificazione:

  • Se si esegue la misurazione dopo un'operazione di Garbage Collection di generazione 2, l'intero heap gestito sarà libero da oggetti non utilizzati.

  • Se si esegue la misurazione immediatamente dopo un'operazione di Garbage Collection di generazione 0, gli oggetti delle generazioni 1 e 2 non saranno ancora stati raccolti.

  • Se si esegue la misurazione immediatamente prima di un'operazione di Garbage Collection, verrà misurata tutta l'allocazione possibile prima dell'inizio dell'operazione.

  • La misurazione durante un'operazione di Garbage Collection è problematica, in quanto le strutture di dati del Garbage Collector non si trovano in uno stato valido per l'attraversamento e i risultati potrebbero non essere completi. Tale comportamento è stato definito in fase di progettazione.

  • Quando si utilizza un'operazione di Garbage Collection in modalità simultanea, gli oggetti recuperati non vengono compattati, pertanto le dimensioni dell'heap possono essere uguali o maggiori (è possibile che appaiano maggiori a causa della frammentazione).

  • L'operazione di Garbage Collection simultanea sulla generazione 2 viene ritardata quando il carico della memoria fisica è eccessivo.

Nella procedura seguente viene descritto come impostare un punto di interruzione per poter misurare l'heap gestito.

Per impostare un punto di interruzione alla fine di un'operazione di Garbage Collection

  • In WinDbg con l'estensione del debugger SOS caricata digitare il comando seguente:

    bp mscorwks!WKS::GCHeap::RestartEE "j (dwo(mscorwks!WKS::GCHeap::GcCondemnedGeneration)==2) 'kb';'g'"

    dove il valore di GcCondemnedGeneration è impostato sulla generazione desiderata. Questo comando richiede simboli privati.

    Il comando forza un'interruzione se l'esecuzione di RestartEE avviene dopo che gli oggetti di generazione 2 sono stati recuperati per l'operazione di Garbage Collection.

    In un'operazione di Garbage Collection per server solo un thread chiama RestartEE, pertanto il punto di interruzione si verifica una volta sola durante un'operazione di Garbage Collection di generazione 2.

Torna all'inizio

Procedure di controllo delle prestazioni

In questa sezione vengono descritte le procedure seguenti per isolare la causa del problema di prestazioni:

  • Determinare se il problema è causato da un'operazione di Garbage Collection.

  • Determinare se l'eccezione di memoria insufficiente è gestita.

  • Determinare la quantità di memoria virtuale che è possibile riservare.

  • Determinare se la memoria fisica disponibile è sufficiente.

  • Determinare la quantità di memoria di cui viene eseguito il commit dall'heap gestito.

  • 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 vi sono oggetti in attesa di finalizzazione.

  • Determinare la quantità di spazio disponibile nell'heap gestito.

  • Determinare il numero di oggetti bloccati.

  • Determinare la durata di un'operazione di Garbage Collection.

  • Determinare la causa di un'operazione di Garbage Collection.

  • Determinare se l'elevato utilizzo della CPU è causato da un'operazione di Garbage Collection.

Per determinare se il problema è causato da un'operazione di Garbage Collection

  • Esaminare i due contatori delle prestazioni di memoria seguenti:

    • % tempo in GC. Indica la percentuale di tempo trascorso per l'esecuzione di un'operazione di Garbage Collection dopo l'ultimo ciclo di Garbage Collection. Utilizzare questo contatore per determinare se viene utilizzato troppo tempo dal Garbage Collector per rendere disponibile lo spazio dell'heap gestito. Se il tempo utilizzato per l'operazione di Garbage Collection è relativamente basso, potrebbe essere presente un problema di risorse al di fuori dell'heap gestito. Questo contatore potrebbe non essere accurato in caso di operazioni di Garbage Collection simultanee o in background.

    • Totale byte di cui è stato eseguito il commit. Indica la quantità di memoria virtuale di cui è stato attualmente eseguito il commit da parte del Garbage Collector. Utilizzare questo contatore per determinare se la memoria utilizzata dal Garbage Collector costituisce una parte eccessiva della memoria utilizzata dall'applicazione.

    La maggior parte dei contatori delle prestazioni di memoria viene aggiornata alla fine di ogni operazione di Garbage Collection. I dati potrebbero pertanto non riflettere le condizioni correnti su cui si desidera ottenere informazioni.

Per determinare se l'eccezione di memoria insufficiente è gestita

  1. In WinDbg o nel debugger di Visual Studio con l'estensione del debugger SOS caricata digitare il comando di stampa dell'eccezione (pe):

    !pe

    Se l'eccezione è gestita, viene visualizzato OutOfMemoryException come tipo di eccezione, come illustrato nell'esempio seguente.

    Exception object: 39594518
    Exception type: System.OutOfMemoryException
    Message: <none>
    InnerException: <none>
    StackTrace (generated):
    
  2. Se l'output non specifica un'eccezione, è necessario determinare il thread da cui proviene l'eccezione di memoria insufficiente. Digitare il comando seguente nel debugger per visualizzare tutti i thread con i relativi stack di chiamate:

    ~*kb

    Il thread con lo stack in cui vi sono chiamate dell'eccezione è indicato dall'argomento RaiseTheException. Si tratta dell'oggetto eccezione gestita.

    28adfb44 7923918f 5b61f2b4 00000000 5b61f2b4 mscorwks!RaiseTheException+0xa0 
    
  3. È possibile utilizzare il comando seguente per eseguire il dump delle eccezioni annidate.

    !pe -nested

    Se non viene trovata alcuna eccezione, l'eccezione di memoria insufficiente proviene dal codice non gestito.

Per determinare la quantità di memoria virtuale che è possibile riservare

  • In WinDbg con l'estensione del debugger SOS caricata digitare il comando seguente per ottenere l'area libera di dimensioni maggiori:

    !address -summary

    L'area libera di dimensioni maggiori verrà visualizzata come illustrato nell'output seguente.

    Largest free region: Base 54000000 - Size 0003A980
    

    In questo esempio la dimensione dell'area libera più grande è di circa 24000 KB (3A980 in formato esadecimale). Quest'area è molto più piccola di quella necessaria al Garbage Collector per un segmento.

    - oppure -

  • Utilizzare il comando vmstat:

    !vmstat

    L'area libera di dimensioni maggiori è il valore più grande nella colonna MAXIMUM, come illustrato 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 la memoria fisica disponibile è sufficiente

  1. Avviare Gestione attività Windows.

  2. Nella scheda Prestazioni osservare il valore di cui è stato eseguito il commit. In Windows 7 osservare Commit (KB) nel gruppo Sistema.

    Se il valore Totale è vicino a quello Limite, la memoria fisica inizia a diventare insufficiente.

Per determinare la quantità di memoria di cui viene eseguito il commit dall'heap gestito

  • Utilizzare il contatore delle prestazioni di memoria # Total committed bytes per ottenere il numero di byte di cui viene eseguito il commit da parte dell'heap gestito. Tramite il Garbage Collector viene eseguito il commit di blocchi in un segmento in base a quanto necessario, non di tutti contemporaneamente.

    NotaNota

    Non utilizzare il contatore delle prestazioni # Bytes in all Heaps, poiché non rappresenta l'utilizzo di memoria effettivo da parte dell'heap gestito.La dimensione di una generazione è inclusa in questo valore e corrisponde effettivamente alla dimensione di soglia, ovvero la dimensione che provoca un'operazione di Garbage Collection se la generazione è piena di oggetti.Pertanto questo valore è in genere zero.

Per determinare la quantità di memoria riservata dall'heap gestito

  • Utilizzare il contatore delle prestazioni di memoria # Total reserved bytes.

    La memoria viene riservata dal Garbage Collector in segmenti ed è possibile determinare l'inizio di un segmento utilizzando il comando eeheap.

  • In WinDbg o nel debugger di Visual Studio con l'estensione del debugger SOS caricata digitare il comando seguente:

    !eeheap -gc

    Il risultato è illustrato di seguito.

    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

  • In WinDbg o nel debugger di Visual Studio con l'estensione del debugger SOS caricata digitare il comando seguente:

    !dumpheap –stat

    Se l'heap gestito è di grandi dimensioni, il completamento di dumpheap potrebbe richiedere del tempo.

    È possibile iniziare l'analisi dalle ultime righe dell'output, poiché in queste righe sono elencati gli oggetti che utilizzano 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 capire come ottimizzare gli oggetti stringa. Per visualizzare le stringhe di dimensioni comprese tra 150 e 200 byte, digitare il comando seguente:

    !dumpheap -type System.String -min 150 -max 200

    Di seguito è illustrato un esempio dei risultati.

    Address  MT           Size  Gen
    1875d2c0 790fa3e0      152    2 System.String HighlightNullStyle_Blotter_PendingOrder-11_Blotter_PendingOrder-11
    …
    

    L'utilizzo di un intero al posto di una stringa per un ID può essere più efficiente. Se la stessa stringa viene ripetuta migliaia di volte, prendere in considerazione l'inserimento di stringa. Per ulteriori informazioni sull'inserimento di stringa, 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 digitare il comando seguente per elencare i riferimenti agli oggetti:

    !gcroot

    -or-

  • Per determinare i riferimenti per un oggetto specifico, includere l'indirizzo:

    !gcroot 1c37b2ac

    Le radici trovate negli stack possono essere falsi positivi. Per ulteriori informazioni, utilizzare 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 completamento del comando gcroot può richiedere molto tempo. Qualsiasi oggetto non recuperato da un'operazione di Garbage Collection è un oggetto attivo. Questo significa che qualche radice sta bloccando direttamente o indirettamente l'oggetto, pertanto gcroot deve restituire le informazioni sul percorso all'oggetto. Analizzare i grafici restituiti per capire il motivo per cui vi sono ancora riferimenti a tali 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 consente di risolvere il problema, significa che non era in corso il recupero degli oggetti da parte del Garbage Collector, poiché i finalizzatori per tali oggetti erano stati sospesi. Il metodo GC.WaitForPendingFinalizers consente ai finalizzatori di completare le attività e permette di risolvere il problema.

Per determinare se vi sono oggetti in attesa di finalizzazione

  1. In WinDbg o nel debugger di Visual Studio con l'estensione del debugger SOS caricata digitare il comando seguente:

    !finalizequeue

    Osservare il numero di oggetti pronti per la finalizzazione. Se il numero è elevato, è necessario analizzare il motivo per cui i finalizzatori non possono avanzare in alcun modo o non possono avanzare abbastanza rapidamente.

  2. Per ottenere un output dei thread, digitare il comando seguente:

    threads -special

    Questo comando fornisce un output analogo a quello illustrato di seguito.

           OSID     Special thread type
        2    cd0    DbgHelper 
        3    c18    Finalizer 
        4    df0    GC SuspendEE 
    

    Il thread finalizzatore indica quale finalizzatore, se presente, è attualmente in esecuzione. Quando tramite un thread finalizzatore non viene eseguito alcun finalizzatore, significa che il thread è in attesa di un evento che richieda l'esecuzione dell'attività. Il thread finalizzatore si trova in questo stato per la maggior parte del tempo, in quanto viene eseguito in base all'opzione THREAD_HIGHEST_PRIORITY e pertanto il completamento dell'esecuzione dei finalizzatori, se presenti, avviene molto rapidamente.

Per determinare la quantità di spazio disponibile nell'heap gestito

  • In WinDbg o nel debugger di Visual Studio con l'estensione del debugger SOS caricata digitare il comando seguente:

    !dumpheap -type Free -stat

    Questo comando consente di visualizzare la dimensione totale di tutti gli oggetti liberi nell'heap gestito, come illustrato nell'esempio seguente.

    total 230 objects
    Statistics:
          MT    Count    TotalSize Class Name
    00152b18      230     40958584      Free
    Total 230 objects
    
  • Per determinare lo spazio disponibile nella generazione 0, digitare il comando seguente per informazioni sul consumo di memoria in base alla generazione:

    !eeheap -gc

    Questo comando consente di visualizzare un output analogo a quello illustrato di seguito. L'ultima riga indica il segmento temporaneo.

    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 utilizzato dalla generazione 0:

    ? 49e05d04-0x49521f8c

    Il risultato è illustrato di seguito. La generazione 0 ha una dimensione di circa 9 MB.

    Evaluate expression: 9321848 = 008e3d78
    
  • Il comando seguente consente di eseguire il dump dello spazio disponibile nell'intervallo della generazione 0:

    !dumpheap -type Free -stat 0x49521f8c 49e05d04

    Il risultato è illustrato di seguito.

    ------------------------------
    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 indica che nella parte di generazione 0 dell'heap sono utilizzati 9 MB di spazio per gli oggetti e vi sono 7 MB disponibili. Questa analisi indica in che misura la generazione 0 contribuisce alla frammentazione. Questa quantità di utilizzo dell'heap deve essere sottratta dalla quantità totale come causa della frammentazione dovuta a oggetti a lungo termine.

Per determinare il numero di oggetti bloccati

  • In WinDbg o nel debugger di Visual Studio con l'estensione del debugger SOS caricata digitare il comando seguente:

    !gchandles

    Le statistiche visualizzate includono il numero di handle bloccati, come illustrato nell'esempio seguente.

    GC Handle Statistics:
    Strong Handles:      29
    Pinned Handles:      10
    

Per determinare la durata di un'operazione di Garbage Collection

  • Analizzare il contatore delle prestazioni di memoria % Time in GC.

    Il valore è calcolato utilizzando il tempo di intervallo di un campione. Poiché i contatori vengono aggiornati alla fine di ogni operazione di Garbage Collection, il campione corrente avrà lo stesso valore di quello precedente se non sono state eseguite operazioni di raccolta durante l'intervallo.

    Il tempo di raccolta viene ottenuto moltiplicando il tempo di intervallo del campione con il valore percentuale.

    I dati seguenti illustrano quattro intervalli di campionamento di due secondi, per uno studio di 8 secondi. Le colonne Gen0, Gen1 e Gen2 indicano il numero di operazioni di Garbage Collection eseguite durante l'intervallo per tale 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 è stata eseguita l'operazione di Garbage Collection, ma è possibile determinare il numero di operazioni eseguite in un intervallo di tempo. Presupponendo il caso peggiore, la decima operazione di Garbage Collection di generazione 0 è terminata all'inizio del secondo intervallo e l'undicesima operazione è terminata alla fine del quinto intervallo. Il tempo tra la fine della decima operazione di Garbage Collection e la fine dell'undicesima operazione è di circa 2 secondi e il contatore delle prestazioni indica 3%, pertanto la durata dell'undicesima operazione di Garbage Collection di generazione 0 è stata (2 secondi * 3% = 60 ms).

    In questo esempio vi sono 5 periodi.

    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
    

    La seconda operazione di Garbage Collection di generazione 2 è iniziata durante il terzo intervallo e terminata in corrispondenza del quinto intervallo. Presupponendo il caso peggiore, l'ultima operazione di Garbage Collection è stata per una raccolta di generazione 0 terminata all'inizio del secondo intervallo e l'operazione di Garbage Collection di generazione 2 è terminata alla fine del quinto intervallo. Di conseguenza, il tempo tra la fine dell'operazione di Garbage Collection di generazione 0 e la fine dell'operazione di Garbage Collection di generazione 2 è di 4 secondi. Poiché il contatore % Time in GC ha un valore del 20%, la quantità massima di tempo che può essere stata utilizzata dall'operazione di Garbage Collection di generazione 2 è di (4 secondi * 20% = 800 ms).

  • In alternativa, è possibile determinare la durata di un'operazione di Garbage Collection utilizzando gli eventi ETW di Garbage Collection e analizzando le informazioni per determinare la durata.

    I dati seguenti illustrano, ad esempio, 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'operazione effettiva 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 fornisce un esempio di operazione di Garbage Collection in background e include campi evento, thread e processo. Non sono riportati tutti i dati.

    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, in quanto l'ultimo campo è 1. Questa diventa l'operazione di Garbage Collection numero 102019.

    L'evento GCStart si verifica poiché è necessaria un'operazione di Garbage Collection temporanea prima dell'avvio di un'operazione in background. Questa diventa l'operazione di Garbage Collection numero 102020.

    In corrispondenza di 42514170, l'operazione di Garbage Collection numero 102020 termina. I thread gestiti vengono riavviati in questo punto. Il processo viene completato nel thread 4372, da cui è stata attivata l'operazione di Garbage Collection in background.

    In corrispondenza del thread 4744 si verifica una sospensione. Questa è l'unica occasione in cui i thread gestiti devono essere sospesi dall'operazione di Garbage Collection in background. La durata è approssimativamente di 99 ms ((63784407-63685394)/1000).

    L'evento GCEnd per l'operazione di Garbage Collection in background è in corrispondenza di 89931423. Questo significa che l'operazione di Garbage Collection in background è durata circa 47 secondi ((89931423-42504816)/1000).

    Mentre i thread gestiti sono in esecuzione, può venire eseguito un numero qualsiasi di operazioni di Garbage Collection temporanee.

Per determinare la causa di un'operazione di Garbage Collection

  • In WinDbg o nel debugger di Visual Studio con l'estensione del debugger SOS caricata digitare il comando seguente per visualizzare tutti i thread con i relativi stack di chiamate:

    ~*kb

    Questo comando consente di visualizzare un output analogo a quello illustrato di seguito.

    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 causata da una notifica di memoria insufficiente inviata dal sistema operativo, lo stack di chiamate è simile, ad eccezione del fatto che il thread è quello finalizzatore. Il thread finalizzatore riceve una notifica asincrona di memoria insufficiente e provoca l'operazione di Garbage Collection.

    Se l'operazione di Garbage Collection è stata provocata dall'allocazione di memoria, lo stack è come indicato di seguito:

    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 infine GCHeap::GarbageCollectGeneration. Se si determina che le operazioni di Garbage Collection di generazione 2 sono causate dalle allocazioni, è necessario determinare quali oggetti vengono raccolti da un'operazione di Garbage Collection di generazione 2 e come evitarli. Ciò significa che è necessario determinare la differenza tra l'inizio e la fine di un'operazione di Garbage Collection di generazione 2 e gli oggetti che hanno causato la raccolta di generazione 2.

    Digitare, ad esempio, il comando seguente nel debugger per visualizzare l'inizio di una raccolta di generazione 2:

    !dumpheap –stat

    Output di esempio (ridotto per mostrare gli oggetti che utilizzano 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 (ridotto per mostrare gli oggetti che utilizzano 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[] non sono più presenti alla fine dell'output e questo significa che sono stati raccolti. Le dimensioni di questi oggetti sono approssimativamente di 70 MB. Gli oggetti rimanenti non sono cambiati molto. Questi oggetti double[] sono stati pertanto la causa dell'esecuzione dell'operazione di Garbage Collection di generazione 2. Il passaggio successivo consiste nel determinare il motivo della presenza degli oggetti double[] e perché gli oggetti sono inutilizzati. È possibile rivolgersi allo sviluppatore del codice per conoscere la provenienza degli oggetti oppure utilizzare il comando gcroot.

Per determinare se l'elevato utilizzo della CPU è causato da un'operazione di Garbage Collection

  • Mettere in correlazione il valore del contatore delle prestazioni di memoria % Time in GC con il tempo dei processi.

    Se il valore di % Time in GC raggiunge i picchi contemporaneamente al tempo dei processi, un'operazione di Garbage Collection sta provocando un elevato utilizzo della CPU. In caso contrario, profilare l'applicazione per determinare dove si verifica l'utilizzo elevato.

Vedere anche

Concetti

Garbage Collection