Condividi tramite


Garbage Collection e prestazioni

Questo articolo descrive i problemi relativi all'utilizzo di Garbage Collection e della memoria. Affronta i problemi che riguardano l'heap gestito e spiega come ridurre al minimo l'effetto della raccolta dei rifiuti sulle applicazioni. Ogni problema include collegamenti a procedure che è possibile usare per analizzare i problemi.

Strumenti di analisi delle prestazioni

Le sezioni seguenti descrivono gli strumenti disponibili per analizzare l'utilizzo della memoria e i problemi di Garbage Collection. Le procedure fornite più avanti in questo articolo fanno riferimento a questi strumenti.

Contatori delle prestazioni della memoria

È possibile usare i contatori delle prestazioni per raccogliere dati sulle prestazioni. Per istruzioni, vedere Profiling di runtime. La categoria Memoria CLR .NET dei contatori delle prestazioni, come descritto in Contatori delle prestazioni in .NET, fornisce informazioni sul Garbage Collector.

Debug con SOS

È possibile usare windows Debugger (WinDbg) per controllare gli oggetti nell'heap gestito.

Per installare WinDbg, installare Strumenti di debug per Windows dalla pagina Scarica strumenti di debug per Windows .

Eventi ETW di Garbage Collection

Event Tracing for Windows (ETW) è un sistema di traccia che integra il supporto di profilatura e debug fornito 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 si verifica un'operazione di Garbage Collection, fornisce le informazioni seguenti:

  • Quale generazione di oggetti viene raccolta?
  • Cosa ha attivato l'operazione di Garbage Collection.
  • Tipo di raccolta dei rifiuti di memoria (concurrente o non concurrente).

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

L'API di profilazione

Le interfacce di profilatura CLR (Common Language Runtime) forniscono informazioni dettagliate sugli oggetti interessati durante la Garbage Collection. Un profiler può ricevere una notifica quando una raccolta dei rifiuti inizia e finisce. Può fornire report sugli oggetti nell'heap gestito, inclusa l'identificazione di oggetti in ogni generazione. Per altre informazioni, vedere Panoramica della 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 applicazione consente agli host di monitorare l'utilizzo della CPU e della memoria in base al dominio applicazione. Per altre informazioni, vedere Application Domain Resource Monitoring.

Risolvere i problemi di prestazioni

Il primo passaggio consiste nel determinare se il problema è effettivamente Garbage Collection. Se si determina che è necessario, scegliere dall'elenco seguente per risolvere il problema.

Problema: viene generata un'eccezione di memoria insufficiente

Esistono due casi legittimi per generare un'eccezione gestita OutOfMemoryException :

  • Memoria virtuale insufficiente.

    Il Garbage Collector alloca memoria dal sistema in segmenti di una dimensione predefinita. Se un'allocazione richiede un segmento aggiuntivo, ma non esiste un blocco libero contiguo lasciato nello spazio di memoria virtuale del processo, l'allocazione per l'heap gestito avrà esito negativo.

  • Non c'è abbastanza memoria fisica da allocare.

Controlli delle prestazioni
Determinare se l'eccezione di memoria insufficiente è gestita.
Determinare la quantità di memoria virtuale che può essere riservata.
Determinare se la memoria fisica è sufficiente.

Se si determina che l'eccezione non è legittima, contattare il servizio clienti Microsoft e il supporto tecnico con le informazioni seguenti:

  • Stack con l'eccezione gestita di esaurimento della memoria.
  • Dump completo della memoria.
  • Dati che dimostrano che non si tratta di un'eccezione legittima di memoria insufficiente, inclusi i dati che mostrano che la memoria virtuale o fisica non è un problema.

Problema: il processo usa una quantità eccessiva di memoria

Un presupposto comune è che l'utilizzo della memoria visualizzato nella scheda Prestazioni di Gestione attività di Windows può indicare quando viene usata una quantità eccessiva di memoria. Tuttavia, tale visualizzazione riguarda il working set; non fornisce informazioni sull'utilizzo della 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 usare il debug nativo.

Controlli delle prestazioni
Determinare la quantità di memoria virtuale che può essere riservata.
Determinare la quantità di memoria impegnata dell'heap gestito.
Determinare la quantità di memoria riservata dall'heap gestito.
Determinare oggetti di grandi dimensioni nella generazione 2.
Determinare i riferimenti agli oggetti.

Problema: Il Garbage Collector non recupera oggetti abbastanza velocemente

Quando sembra che gli oggetti non vengano rilasciati come previsto per la garbage collection, è necessario determinare se sono presenti riferimenti forti a tali oggetti.

È anche possibile che si verifichi questo problema se non è stata eseguita alcuna Garbage Collection per la generazione che contiene un oggetto morto, che indica che il finalizzatore per l'oggetto non è stato eseguito. Ad esempio, ciò è possibile quando si esegue un'applicazione in un appartamento a thread singolo (STA) e il thread che gestisce la coda del finalizzatore non può accedervi.

Controlli delle prestazioni
Controllare i riferimenti agli oggetti.
Verificare se un finalizzatore è stato eseguito.
Determinare se sono presenti oggetti in attesa di essere finalizzati.

Problema: l'heap gestito è troppo frammentato

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

La presenza di un sacco di spazio libero nella generazione 0 non è un problema perché si tratta della generazione in cui vengono allocati nuovi oggetti.

La frammentazione si verifica sempre nell'heap di oggetti di grandi dimensioni perché non è compattata. Gli oggetti liberi adiacenti vengono naturalmente compressi in un unico spazio per soddisfare richieste di allocazione di oggetti di grandi dimensioni.

La frammentazione può diventare un problema nella generazione 1 e nella generazione 2. Se queste generazioni hanno una grande quantità di spazio disponibile dopo un'operazione di Garbage Collection, l'utilizzo degli oggetti di un'applicazione potrebbe richiedere modifiche ed è consigliabile valutare nuovamente la durata degli oggetti a lungo termine.

Il bloccaggio eccessivo di oggetti può aumentare la frammentazione. Se la frammentazione è elevata, troppi oggetti potrebbero essere stati bloccati.

Se la frammentazione della memoria virtuale impedisce al Garbage Collector di aggiungere segmenti, le cause potrebbero essere una delle seguenti:

  • Caricamento e scaricamento frequente di molti piccoli assemblaggi.

  • Contiene troppi riferimenti agli oggetti COM durante l'interoperabilità con codice non gestito.

  • Creazione di oggetti transitori di grandi dimensioni, che causa frequentemente l'allocazione e la liberazione di segmenti di heap di grandi dimensioni.

    Quando si ospita CLR, un'applicazione può richiedere che il Garbage Collector mantenga i segmenti. In questo modo si riduce la frequenza delle allocazioni di segmenti. A tale scopo, usare il flag STARTUP_HOARD_GC_VM nell'enumerazione STARTUP_FLAGS.

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 alcuna causa legittima per la frammentazione, contattare il servizio clienti Microsoft e il supporto tecnico.

Problema: Le pause di Garbage Collection sono troppo lunghe

La raccolta dei rifiuti opera in tempo reale moderato, pertanto, un'applicazione deve essere capace di tollerare alcune pause. Un criterio per il tempo reale flessibile è che 95% delle operazioni devono terminare in tempo.

In Garbage Collection simultanei, i thread gestiti possono essere eseguiti durante una raccolta, il che significa che le pause sono molto minime.

Le raccolte di rifiuti effimeri (generazioni 0 e 1) durano solo pochi millisecondi, quindi ridurre le pause di solito non riesce. Tuttavia, è possibile ridurre le pause nelle raccolte di seconda generazione modificando il modello di richieste di allocazione da parte di un'applicazione.

Un altro metodo, più accurato, consiste nell'usare gli eventi ETW di Garbage Collection. È possibile trovare i tempi per le raccolte aggiungendo le differenze di timestamp per una sequenza di eventi. L'intera sequenza di raccolta include la sospensione del motore di esecuzione, la raccolta stessa e la ripresa del motore di esecuzione.

È possibile usare le notifiche di Garbage Collection per determinare se un server sta per avere una raccolta di seconda generazione e se reindirizzare le richieste a un altro server potrebbe risolvere eventuali problemi con le pause.

Controlli delle prestazioni
Determinare il periodo di tempo in un'operazione di Garbage Collection.
Determinare la causa di un'operazione di Garbage Collection.

Problema: la generazione 0 è troppo grande

È probabile che la generazione 0 abbia un numero maggiore di oggetti in un sistema a 64 bit, soprattutto quando si usa Garbage Collection del server invece di Garbage Collection della workstation. Ciò è dovuto al fatto che la soglia per attivare un'operazione di Garbage Collection di generazione 0 è superiore in questi ambienti e le raccolte di generazione 0 possono aumentare notevolmente. Le prestazioni vengono migliorate quando un'applicazione alloca più 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 sarà elevato durante un'operazione di Garbage Collection. Se una quantità significativa di tempo di processo viene impiegato in un'operazione di Garbage Collection, il numero di raccolte è troppo frequente o la raccolta dura troppo a lungo. Un aumento della frequenza di allocazione degli oggetti nell'heap gestito causa un'operazione di Garbage Collection più frequente. Riducendo il tasso di allocazione, si riduce la frequenza della Garbage Collection.

È possibile monitorare i tassi di allocazione usando il Allocated Bytes/second contatore delle prestazioni. Per altre informazioni, vedere Contatori delle prestazioni in .NET.

La durata di una raccolta è principalmente un fattore del numero di oggetti che sopravvivono dopo l'allocazione. Il Garbage Collector deve passare attraverso una grande quantità di memoria se molti oggetti rimangono da raccogliere. Il lavoro per compattare i sopravvissuti richiede molto tempo. Per determinare il numero di oggetti gestiti durante una raccolta, impostare un punto di interruzione nel debugger alla fine di un'operazione di Garbage Collection per una generazione specificata.

Controlli delle prestazioni
Determinare se l'utilizzo elevato della CPU è causato dalla raccolta dei rifiuti.
Impostare un punto di interruzione alla fine di Garbage Collection.

Linee guida per la risoluzione dei problemi

Questa sezione descrive le linee guida da considerare durante l'avvio delle indagini.

Raccolta dei rifiuti della workstation o del server

Determinare se si sta usando il tipo corretto di raccolta dei rifiuti. Se l'applicazione usa più thread e istanze di oggetti, usare Garbage Collection del server invece di Garbage Collection della workstation. Il Garbage Collection del server opera su più thread, mentre la Garbage Collection della workstation richiede più istanze di un'applicazione per eseguire i propri thread di Garbage Collection e competere per il tempo della CPU.

Un'applicazione con un carico ridotto e che esegue raramente attività in background, ad esempio un servizio, potrebbe usare workstation garbage collection con garbage collection simultaneo disabilitato.

Quando misurare le dimensioni dell'heap gestito

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

  • Se si misura dopo un'operazione di Garbage Collection di seconda generazione, l'intero heap gestito sarà privo di garbage (oggetti inattivi).
  • Se si misura immediatamente dopo un'operazione di Garbage Collection di generazione 0, gli oggetti nelle generazioni 1 e 2 non verranno ancora raccolti.
  • Se si misura immediatamente prima di una Garbage Collection, si potranno misurare quante più allocazioni possibili prima dell'avvio della Garbage Collection.
  • La misurazione durante una raccolta dei rifiuti è problematica perché le strutture dati del lettore dei rifiuti non sono in un stato valido per essere attraversate e potrebbero non essere in grado di fornire risultati completi. Questo è intenzionale.
  • Quando si utilizza la raccolta dei rifiuti della workstation con la raccolta dei rifiuti simultanea, gli oggetti recuperati non vengono compattati, quindi le dimensioni dell'heap possono essere uguali o maggiori (la frammentazione può far sembrare più grandi le dimensioni).
  • La Garbage Collection simultanea alla generazione 2 viene ritardata quando il carico di memoria fisica è troppo elevato.

La procedura seguente descrive come impostare un punto di interruzione in modo da poter misurare l'heap gestito.

Per impostare un punto di interruzione alla fine della raccolta dei rifiuti

  • 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.

    Questo comando forza un'interruzione se RestartEE viene eseguita dopo che gli oggetti di seconda generazione sono stati recuperati per l'operazione di Garbage Collection.

    Nella raccolta dei rifiuti del server, un solo thread chiama RestartEE quindi il breakpoint verrà eseguito una sola volta durante una raccolta dei rifiuti di seconda generazione.

Procedure di controllo delle prestazioni

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

Per determinare se il problema è causato da Garbage Collection

  • Esaminare i due contatori delle prestazioni di memoria seguenti:

    • % Ora nel GC. Visualizza la percentuale di tempo trascorso dedicato all'esecuzione di una garbage collection dopo l'ultimo ciclo di raccolta. Usare questo contatore per determinare se il raccoglitore di immondizia sta spendendo troppo tempo per rendere disponibile lo spazio dell'heap gestito. Se il tempo impiegato in garbage collection è relativamente basso, questo potrebbe indicare un problema di risorse all'esterno dell'heap gestito. Questo contatore potrebbe non essere accurato quando è coinvolta un'operazione di Garbage Collection simultanea o in background.

    • # Totale byte di cui è stato eseguito il commit. Visualizza la quantità di memoria virtuale attualmente sottoposta a commit dal Garbage Collector. Usare questo contatore per determinare se la memoria utilizzata dal Garbage Collector è una parte eccessiva della memoria usata dall'applicazione.

    La maggior parte dei contatori delle prestazioni di memoria viene aggiornata alla fine di ogni Garbage Collection. Pertanto, potrebbero non riflettere le condizioni correnti su cui si desiderano informazioni.

Per determinare se l'eccezione di memoria insufficiente è gestita

  1. Nel debugger WinDbg o Visual Studio con l'estensione debugger SOS caricata, immettere il comando print exception (pe):

    !pe

    Se l'eccezione è gestita, OutOfMemoryException viene visualizzata 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. Immettere il comando seguente nel debugger per visualizzare tutti i thread con i relativi stack di chiamate:

    ~\*kb

    Il thread dello stack contenente chiamate di eccezione è indicato dall'argomento RaiseTheException. Questo è l'oggetto di eccezione gestito.

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

    !pe -nested

    Se non vengono trovate eccezioni, l'eccezione di esaurimento della memoria ha avuto origine da codice non gestito.

Per determinare la quantità di memoria virtuale che può essere riservata

  • In WinDbg con l'estensione del debugger SOS caricata immettere il comando seguente per ottenere l'area gratuita più grande:

    !address -summary

    L'area gratuita più grande viene 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 esadecimale). Questa area è molto più piccola rispetto a quanto richiesto dal Garbage Collector per un segmento.

    -o-

  • Usare il comando vmstat:

    !vmstat

    L'area libera più grande è 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 è sufficiente

  1. Avvia Gestione attività di Windows.

  2. Sulla scheda Performance, esamina il valore impegnato. (In Windows 7, consultare Commit (KB) in System group.)

    Total Se è vicino a Limit, la memoria fisica è insufficiente.

Per determinare la quantità di memoria impegnata dell'heap gestito

  • Usare il # Total committed bytes contatore delle prestazioni della memoria per determinare il numero di byte che l'heap gestito sta impegnando. Il Garbage Collector esegue il commit di blocchi in un segmento in base alle esigenze, non tutti contemporaneamente.

    Annotazioni

    Non usare il # Bytes in all Heaps contatore delle prestazioni, perché non rappresenta l'utilizzo effettivo della memoria dall'heap gestito. La dimensione di una generazione è inclusa in questo valore ed è effettivamente la dimensione soglia, ovvero la dimensione che induce una raccolta di rifiuti se la generazione viene riempita di oggetti. Pertanto, questo valore è in genere zero.

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

  • Utilizza il contatore delle prestazioni della memoria # Total reserved bytes.

    Il Garbage Collector riserva memoria in segmenti ed è possibile determinare dove inizia un segmento usando il eeheap comando .

    Importante

    Sebbene sia possibile determinare la quantità di memoria allocata dal Garbage Collector per ogni segmento, le dimensioni del segmento sono specifiche dell'implementazione e sono soggette a modifiche in qualsiasi momento, inclusi gli aggiornamenti periodici. L'app non deve mai fare ipotesi su o dipendere da una determinata dimensione del segmento, né deve tentare di configurare la quantità di memoria disponibile per le allocazioni dei segmenti.

  • Nel debugger WinDbg o 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 "segmento" sono gli indirizzi iniziali dei segmenti.

Per determinare oggetti di grandi dimensioni nella generazione 2

  • Nel debugger WinDbg o Visual Studio con l'estensione del debugger SOS caricata immettere il comando seguente:

    !dumpheap –stat

    Se l'heap gestito è grande, dumpheap potrebbe richiedere un po' di tempo per completarsi.

    È possibile iniziare ad analizzare le ultime righe dell'output, perché elencano gli oggetti che usano il maggior numero di spazio. Per 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 vedere come è possibile 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 è 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ò essere più efficiente. Se la stessa stringa viene ripetuta migliaia di volte, prendere in considerazione l'internamento delle stringhe. Per ulteriori informazioni sull'internamento delle stringhe, vedere l'argomento di riferimento per il String.Intern metodo.

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 risultare 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 completamento del gcroot comando può richiedere molto tempo. Qualsiasi oggetto non recuperato da Garbage Collection è un oggetto attivo. Ciò significa che una radice è direttamente o indirettamente in possesso dell'oggetto, pertanto gcroot deve restituire le informazioni sul percorso all'oggetto. È consigliabile esaminare i grafici restituiti e verificare il motivo per cui questi oggetti sono ancora a cui si fa riferimento.

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 il Garbage Collector non recuperava oggetti, perché i finalizzatori per tali oggetti erano stati sospesi. Il GC.WaitForPendingFinalizers metodo consente ai finalizzatori di completare le attività e risolve il problema.

Per determinare se sono presenti oggetti in attesa di essere finalizzati

  1. Nel debugger WinDbg o Visual Studio con l'estensione del debugger SOS caricata immettere il comando seguente:

    !finalizequeue

    Esaminare il numero di oggetti pronti per la finalizzazione. Se il numero è elevato, è necessario esaminare il motivo per cui questi finalizzatori non possono progredire affatto o non possono progredire abbastanza velocemente.

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

    !threads -special

    Questo comando fornisce un output simile al seguente.

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

    Il thread del finalizzatore indica se è in esecuzione un finalizzatore, e in tal caso quale. Quando un thread finalizzatore non esegue alcun finalizzatore, è in attesa di un evento che gli indichi di svolgere il proprio lavoro. Nella maggior parte dei casi, il thread finalizzatore verrà visualizzato in questo stato perché viene eseguito in THREAD_HIGHEST_PRIORITY e dovrebbe terminare l'esecuzione dei finalizzatori, se presenti, molto rapidamente.

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

  • Nel debugger WinDbg o 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 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, immettere il comando seguente per informazioni sull'utilizzo della memoria per generazione:

    !eeheap -gc

    Questo comando visualizza un output simile al seguente. L'ultima riga mostra 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 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 disponibile all'interno dell'intervallo di 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 gratuiti. Questa analisi mostra la misura in cui la generazione 0 contribuisce alla frammentazione. Questa quantità di utilizzo dell'heap deve essere ridotta dall'importo totale come causa della frammentazione causata da oggetti a lungo termine.

Per determinare il numero di oggetti fissati

  • Nel debugger WinDbg o Visual Studio con l'estensione del debugger SOS caricata immettere il comando seguente:

    !gchandles

    Le statistiche visualizzate includono il numero di handle fissati, come illustrato 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 % Time in GC memoria.

    Il valore viene calcolato usando un tempo di intervallo di campionamento. Poiché i contatori vengono aggiornati alla fine di ogni Garbage Collection, l'esempio corrente avrà lo stesso valore dell'esempio precedente se non si sono verificate raccolte durante l'intervallo.

    Il tempo di raccolta viene ottenuto moltiplicando l'intervallo di campionamento con il valore percentuale.

    I dati seguenti mostrano quattro intervalli di campionamento di due secondi, per uno studio di 8 secondi. Le colonne Gen0, Gen1, e Gen2 mostrano il numero totale di raccolte dei rifiuti completato entro la fine dell'intervallo per quella 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 mostrano quando si è verificata la garbage collection, ma è possibile determinare quante garbage collection si sono verificate in un intervallo di tempo. Supponendo il caso peggiore, la decima generazione 0 garbage collection è terminata all'inizio del secondo intervallo e l'undicesima generazione 0 garbage collection è stata completata alla fine del terzo intervallo. Il tempo tra la fine della decima e la fine dell'undicesima raccolta dei rifiuti è di circa 2 secondi, e il contatore delle prestazioni mostra 3%, quindi la durata della raccolta dei rifiuti di generazione 0 dell'undicesima era (2 secondi * 3% = 60ms).

    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 garbage collection di seconda generazione è iniziata durante il quarto intervallo ed è stata completata durante il quinto intervallo. Supponendo che il caso peggiore, l'ultima operazione di Garbage Collection fosse per una raccolta di generazione 0 terminata all'inizio del terzo intervallo e l'operazione di Garbage Collection di seconda generazione è stata completata alla fine del quinto intervallo. Di conseguenza, il tempo compreso tra la fine della garbage collection di generazione 0 e la fine della Garbage Collection di seconda generazione è di 4 secondi. Poiché il % Time in GC contatore è 20%, il tempo massimo che la garbage collection di generazione 2 potrebbe aver impiegato è (4 secondi * 20% = 800ms).

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

    Ad esempio, i dati seguenti mostrano una sequenza di eventi che si è verificata 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 microsecondi (GCSuspendEEEndGCSuspendEEBegin_V1).

    La raccolta dei rifiuti effettiva ha richiesto 4,8 ms (GCEnd_V1GCStart_V1).

    La ripresa dei thread gestiti ha richiesto 21 us (GCRestartEEEndGCRestartEEBegin).

    L'output seguente fornisce un esempio di raccolta dei rifiuti in background e include i campi processo, thread e campi degli eventi. Non vengono visualizzati 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 a 42504816 indica che si tratta di una raccolta dei rifiuti in background, perché l'ultimo campo è 1. Questo diventa Garbage Collection No. 102019.

    L'evento GCStart si verifica perché è necessaria una raccolta dei rifiuti effimera prima di avviare una raccolta dei rifiuti in background. Questa operazione si trasforma nella raccolta dei rifiuti numero 102020.

    Alle ore 42.514.170, il processo di raccolta rifiuti n. 102020 termina. I thread gestiti vengono riavviati a questo punto. Questa operazione è stata completata nel thread 4372, che ha attivato la Garbage Collection in background.

    Nel thread 4744 si verifica una sospensione. Questo è l'unico momento in cui la raccolta dei rifiuti in background deve sospendere i thread gestiti. Questa durata è di circa 99 ms ((63784407-63685394)/1000).

    L'evento GCEnd per l'operazione di Garbage Collection in background si trova in 89931423. Ciò significa che la Garbage Collection in background è durata circa 47 secondi ((89931423-42504816)/1000).

    È possibile visualizzare qualsiasi numero di raccolte di spazzatura temporanee mentre i thread gestiti sono in esecuzione.

Per determinare l'attivazione di un'operazione di Garbage Collection

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

    ~*KB

    Questo comando visualizza 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 la Garbage Collection è stata causata 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 finalizzatore riceve una notifica asincrona di bassa memoria e avvia la raccolta dei rifiuti.

    Se l'operazione di Garbage Collection è stata causata dall'allocazione di memoria, lo stack viene visualizzato come segue:

    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 assistente just-in-time (JIT_New*) alla fine chiama GCHeap::GarbageCollectGeneration. Se si determina che le collezioni di garbage di seconda generazione sono causate dalle allocazioni, è necessario identificare quali oggetti vengono raccolti da una collezione di garbage di seconda generazione e come evitarli. Ciò significa che si desidera determinare la differenza tra l'inizio e la fine di una raccolta di seconda generazione e gli oggetti che hanno causato questa raccolta di seconda generazione.

    Ad esempio, immettere il comando seguente nel debugger per visualizzare l'inizio di una raccolta di seconda generazione:

    !dumpheap –stat

    Output di esempio (abridged 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 (abridged 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 double[] oggetti sono scomparsi dalla fine dell'output, il che significa che sono stati raccolti. Questi oggetti rappresentano circa 70 MB. Gli oggetti rimanenti non cambiavano molto. Pertanto, questi oggetti double[] erano il motivo per cui si è verificata questa raccolta di generazione 2. Il passaggio successivo consiste nel determinare perché gli double[] oggetti sono presenti e perché sono morti. È possibile chiedere allo sviluppatore di codice da cui provengono questi oggetti oppure è possibile usare il gcroot comando .

Per determinare se l'utilizzo elevato della CPU è causato dalla raccolta dei dati inutili

  • Correlare il valore del % Time in GC contatore delle prestazioni della memoria con l'ora del processo.

    Se il % Time in GC valore aumenta contemporaneamente al tempo di elaborazione, l'operazione di Garbage Collection causa un utilizzo elevato della CPU. In caso contrario, profilare l'applicazione per individuare la posizione in cui si verifica l'utilizzo elevato.

Vedere anche