Quando è accettabile chiamare GC.Collect?


166

Il consiglio generale è che non dovresti chiamare GC.Collectdal tuo codice, ma quali sono le eccezioni a questa regola?

Posso solo pensare ad alcuni casi molto specifici in cui potrebbe avere senso forzare una raccolta dei rifiuti.

Un esempio che mi viene in mente è un servizio, che si sveglia a intervalli, esegue alcune attività e poi dorme a lungo. In questo caso, potrebbe essere una buona idea forzare una raccolta per impedire al processo che sarà presto inattivo di conservare più memoria del necessario.

Ci sono altri casi in cui è accettabile chiamare GC.Collect?


Risposte:


153

Se hai buone ragioni per credere che un insieme significativo di oggetti, in particolare quelli di cui sospetti di essere tra le generazioni 1 e 2, sono ora idonei per la raccolta dei rifiuti e che ora sarebbe il momento opportuno per raccogliere in termini di piccole prestazioni .

Un buon esempio di ciò è se hai appena chiuso un modulo di grandi dimensioni. Sai che ora tutti i controlli dell'interfaccia utente possono essere raccolti in modo inutile e una breve pausa poiché il modulo è chiuso probabilmente non sarà evidente per l'utente.

AGGIORNAMENTO 2.7.2018

A partire da .NET 4.5 - c'è GCLatencyMode.LowLatencye GCLatencyMode.SustainedLowLatency. Quando si entra e si esce da una di queste modalità, si consiglia di forzare un GC completo con GC.Collect(2, GCCollectionMode.Forced).

A partire da .NET 4.6 - esiste il GC.TryStartNoGCRegionmetodo (utilizzato per impostare il valore di sola lettura GCLatencyMode.NoGCRegion). Questo può di per sé, eseguire una garbage collection a blocco completo nel tentativo di liberare memoria sufficiente, ma dato che non autorizziamo GC per un periodo, direi che è anche una buona idea eseguire GC completo prima e dopo.

Fonte: ingegnere Microsoft Ben Watson: scrivere codice .NET ad alte prestazioni , 2a edizione. 2018.

Vedere:


8
Secondo il codice sorgente di MS che chiama GC.Collect (2) ogni 850ms va bene. Non ci credi? Quindi vedi PresentationCore.dll, MS.Internal.MemoryPressure.ProcessAdd (). Al momento ho un'app di elaborazione delle immagini (piccole immagini, niente con una pressione di memoria reale) in cui chiamare GC.Collect (2) richiede più di 850 ms e quindi l'intera app viene bloccata da questa (app che trascorre il 99,7% del tempo in GC).
springy76,

36
@ springy76: essere svolto da Microsoft in un unico posto non significa che sia considerato una buona cosa da coloro che danno consigli da Microsoft ...
Jon Skeet,

4
Non mi piace quell'esempio. Qual è il motivo per farlo dopo la chiusura della forma? Un buon esempio che vedo è dopo aver caricato il livello di gioco su XBox o WindowsPhone. Su quelle piattaforme GC funziona dopo aver allocato 1 MB o sth in quel modo. Quindi è bene allocare il più possibile durante il caricamento del livello (mostrando una schermata iniziale) e quindi fare GC.Collect per cercare di evitare le raccolte durante il gioco.
Piotr Perak,

8
@Peri: Il punto di farlo dopo la chiusura del modulo è che hai appena reso un gruppo di oggetti (controlli, i dati che stavi visualizzando) idonei per la garbage collection - quindi chiamando GC.Collectsostanzialmente stai dicendo al garbage collector che conosci meglio di così per un cambiamento. Perché non ti piace l'esempio?
Jon Skeet,

6
@SHCJ: GC.Collect()richiederà che il GC esegua una raccolta completa . Se sai che hai appena reso molti oggetti precedentemente longevi idonei per la garbage collection e ritieni che l'utente abbia meno probabilità di notare una leggera pausa ora rispetto a quella successiva, sembra del tutto ragionevole pensare che ora sia un tempo di richiedere una raccolta piuttosto che lasciarlo accadere in seguito.
Jon Skeet,

50

Uso GC.Collectsolo quando scrivo banchi di prova per performance / profiler grezzi; cioè ho due (o più) blocchi di codice da testare - qualcosa del tipo:

GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
TestA(); // may allocate lots of transient objects
GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
TestB(); // may allocate lots of transient objects
GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
...

In questo modo TestA()e TestB()corri con lo stato il più simile possibile - cioè TestB()non viene martellato solo perché lo ha TestAlasciato molto vicino al punto di ribaltamento.

Un esempio classico potrebbe essere un semplice exe console (un Mainmetodo abbastanza ordinato per essere pubblicato qui per esempio), che mostra la differenza tra concatenazione di stringhe in loop e StringBuilder.

Se avessi bisogno di qualcosa di preciso, sarebbero due test completamente indipendenti, ma spesso questo è sufficiente se vogliamo solo minimizzare (o normalizzare) il GC durante i test per avere un'idea approssimativa del comportamento.

Durante il codice di produzione? Devo ancora usarlo ;-p


1
E probabilmente aggiungerò anche "WaitForPendingFinalizers" (o qualunque cosa sia) in questo caso ;-p
Marc Gravell

29

La migliore pratica è di non forzare una raccolta dei rifiuti nella maggior parte dei casi. (Ogni sistema su cui ho lavorato che aveva forzato la garbage collection, aveva sottolineato problemi che se risolti avrebbero rimosso la necessità di forzare la garbage collection e accelerato notevolmente il sistema.)

Ci sono alcuni casi in cui si sa di più su l'utilizzo della memoria, allora il garbage collector fa. È improbabile che ciò sia vero in un'applicazione multiutente o in un servizio che risponde a più di una richiesta alla volta.

Tuttavia, in alcune elaborazioni di tipo batch , conosci più del GC. Ad esempio, considerare un'applicazione che.

  • Viene fornito un elenco di nomi di file sulla riga di comando
  • Elabora un singolo file, quindi scrive il risultato in un file di risultati.
  • Durante l'elaborazione del file, crea molti oggetti interconnessi che non possono essere raccolti fino al completamento dell'elaborazione del file (ad esempio un albero di analisi)
  • Non mantiene lo stato di corrispondenza tra i file che ha elaborato .

Si può essere in grado di fare un caso (dopo un'attenta) il test che si dovrebbe forzare una garbage collection completa dopo aver elaborare ogni file.

Un altro caso è un servizio che si sveglia ogni pochi minuti per elaborare alcuni elementi e non mantiene alcuno stato mentre è addormentato . Quindi può essere utile forzare una raccolta completa prima di andare a dormire .

L'unica volta che prenderei in considerazione la forzatura di una raccolta è quando so che un sacco di oggetti sono stati creati di recente e pochissimi oggetti sono attualmente referenziati.

Preferirei avere un'API di Garbage Collection quando potrei dare suggerimenti su questo tipo di cose senza dover forzare un GC da solo.

Vedi anche " Rico Mariani's Performance Tidbits "



11

Nei grandi sistemi 24/7 o 24/6 - sistemi che reagiscono ai messaggi, alle richieste RPC o eseguono il polling continuo di un database o di un processo - è utile avere un modo per identificare le perdite di memoria. Per questo, tendo ad aggiungere un meccanismo all'applicazione per sospendere temporaneamente qualsiasi elaborazione e quindi eseguire la garbage collection completa. Ciò pone il sistema in uno stato di riposo in cui la memoria rimanente è o memoria legittimamente longeva (cache, configurazione, ecc.) Oppure viene "trapelata" (oggetti che non sono previsti o desiderati per il root ma che in realtà lo sono).

Avere questo meccanismo rende molto più semplice profilare l'utilizzo della memoria poiché i report non saranno offuscati dal rumore proveniente dall'elaborazione attiva.

Per essere sicuro di ottenere tutta la spazzatura, devi eseguire due raccolte:

GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();

Poiché la prima raccolta causerà la finalizzazione di tutti gli oggetti con finalizzatori (ma in realtà non è spazzatura raccogliere questi oggetti). Il secondo GC raccoglierà questi oggetti finalizzati.


Ho visto la raccolta a due passaggi in un paio di punti, ma dopo aver letto il passaggio nella documentazione MSDN per GC.WaitForPendingFinalizers che dice: "Attendi il completamento di tutti i finalizzatori prima di continuare. Senza questa chiamata a GC.WaitForPendingFinalizers, il ciclo di lavoro in basso potrebbe essere eseguito contemporaneamente ai finalizzatori. Con questa chiamata, il ciclo di lavoro viene eseguito solo dopo che tutti i finalizzatori sono stati chiamati. " Sono solo un tocco paranoico. Conosci una fonte definitiva per fare due passaggi?
jerhewet,

1
@jerhewet: la chiave per capire perché sono necessarie due raccolte è capire cosa succede agli oggetti con i finalizzatori. Purtroppo non ho esattamente quello che stai chiedendo, ma hanno una lettura di questo articolo e questa domanda su SO .
Paul Ruane,

10

Puoi chiamare GC.Collect () quando sai qualcosa sulla natura dell'app che Garbage Collector non conosce. È allettante pensare che, come l'autore, sia molto probabile. Tuttavia, la verità è che il GC equivale a un sistema esperto abbastanza ben scritto e testato, ed è raro che tu sappia qualcosa sui percorsi di codice di basso livello che non ha.

Il miglior esempio che mi viene in mente dove potresti avere qualche informazione in più è un'app che scorre tra periodi di inattività e periodi molto occupati. Volete le migliori prestazioni possibili per i periodi di punta e quindi volete usare il tempo di inattività per fare un po 'di pulizia.

Tuttavia, la maggior parte delle volte il GC è abbastanza intelligente da farlo comunque.


8

Come soluzione per la frammentazione della memoria. Stavo uscendo dalle eccezioni di memoria mentre scrivevo molti dati in un flusso di memoria (lettura da un flusso di rete). I dati sono stati scritti in blocchi 8K. Dopo aver raggiunto 128M, si è verificata un'eccezione anche se era disponibile molta memoria (ma era frammentata). La chiamata a GC.Collect () ha risolto il problema. Sono stato in grado di gestire oltre 1G dopo la correzione.


7

Dai un'occhiata a questo articolo di Rico Mariani. Dà due regole quando chiamare GC.Collect (la regola 1 è: "Non"):

Quando chiamare GC.Collect ()


3
Ci sono già stato. Non sto cercando scuse per fare qualcosa che non dovresti fare, ma vorrei sapere se ci sono casi specifici in cui sarebbe accettabile.
Brian Rasmussen,

5

Un'istanza in cui è quasi necessario chiamare GC.Collect () è quando si automatizza Microsoft Office tramite Interop. Agli oggetti COM per Office non piace rilasciare automaticamente e può comportare che le istanze del prodotto Office occupino grandi quantità di memoria. Non sono sicuro che si tratti di un problema o di progettazione. Ci sono molti post su questo argomento su Internet, quindi non entrerò troppo nei dettagli.

Quando si programma utilizzando Interop, ogni singolo oggetto COM deve essere rilasciato manualmente, di solito tramite l'uso di Marshal.ReleseComObject (). Inoltre, chiamare manualmente Garbage Collection può aiutare a "ripulire" un po '. Chiamare il seguente codice quando hai finito con gli oggetti Interop sembra aiutare un po ':

GC.Collect()
GC.WaitForPendingFinalizers()
GC.Collect()

Nella mia esperienza personale, l'uso di una combinazione di ReleaseComObject e la chiamata manuale della garbage collection riduce notevolmente l'utilizzo della memoria dei prodotti Office, in particolare Excel.


Sì, mi sono imbattuto in questo con .net accedendo a Excel, che funziona anche tramite oggetti COM. È importante notare che questo non funzionerà bene in modalità DEBUG perché le operazioni GC sono limitate lì. Funzionerà come previsto solo in modalità RELEASE. Link rilevanti: stackoverflow.com/questions/17130382/...
Blechdose

5

Stavo facendo alcuni test delle prestazioni su array ed elenco:

private static int count = 100000000;
private static List<int> GetSomeNumbers_List_int()
{
    var lstNumbers = new List<int>();
    for(var i = 1; i <= count; i++)
    {
        lstNumbers.Add(i);
    }
    return lstNumbers;
}
private static int[] GetSomeNumbers_Array()
{
    var lstNumbers = new int[count];
    for (var i = 1; i <= count; i++)
    {
        lstNumbers[i-1] = i + 1;
    }
    return lstNumbers;
}
private static int[] GetSomeNumbers_Enumerable_Range()
{
    return  Enumerable.Range(1, count).ToArray();
}

static void performance_100_Million()
{
    var sw = new Stopwatch();

    sw.Start();
    var numbers1 = GetSomeNumbers_List_int();
    sw.Stop();
    //numbers1 = null;
    //GC.Collect();
    Console.WriteLine(String.Format("\"List<int>\" took {0} milliseconds", sw.ElapsedMilliseconds));

    sw.Reset();
    sw.Start();
    var numbers2 = GetSomeNumbers_Array();
    sw.Stop();
    //numbers2 = null;
    //GC.Collect();
    Console.WriteLine(String.Format("\"int[]\" took {0} milliseconds", sw.ElapsedMilliseconds));

    sw.Reset();
    sw.Start();
//getting System.OutOfMemoryException in GetSomeNumbers_Enumerable_Range method
    var numbers3 = GetSomeNumbers_Enumerable_Range();
    sw.Stop();
    //numbers3 = null;
    //GC.Collect();

    Console.WriteLine(String.Format("\"int[]\" Enumerable.Range took {0} milliseconds", sw.ElapsedMilliseconds));
}

e ho ottenuto il OutOfMemoryExceptionmetodo GetSomeNumbers_Enumerable_Range, l'unica soluzione consiste nel deallocare la memoria tramite:

numbers = null;
GC.Collect();

Perché votare male? la mia risposta è un esempio che dimostra quando chiamare GC. Hai un suggerimento migliore? Ti invitiamo a presentare.
Daniel B,

4

Nel tuo esempio, penso che chiamare GC.Collect non sia il problema, ma piuttosto c'è un problema di progettazione.

Se ti sveglierai a intervalli (orari prestabiliti), il tuo programma dovrebbe essere predisposto per una singola esecuzione (eseguire l'attività una volta) e quindi terminare. Quindi, impostare il programma come attività pianificata per l'esecuzione agli intervalli pianificati.

In questo modo, non devi preoccuparti di chiamare GC.Collect, (cosa che dovresti fare raramente, se non mai).

Detto questo, Rico Mariani ha un ottimo post sul blog su questo argomento, che può essere trovato qui:

http://blogs.msdn.com/ricom/archive/2004/11/29/271829.aspx


3

Un posto utile per chiamare GC.Collect () è un test unitario quando si desidera verificare che non si stia creando una perdita di memoria (ad es. Se si sta facendo qualcosa con WeakReferences o ConditionalWeakTable, codice generato dinamicamente, ecc.).

Ad esempio, ho alcuni test come:

WeakReference w = CodeThatShouldNotMemoryLeak();
Assert.IsTrue(w.IsAlive);
GC.Collect();
GC.WaitForPendingFinalizers();
Assert.IsFalse(w.IsAlive);

Si potrebbe sostenere che l'uso di WeakReferences sia un problema in sé e per sé, ma sembra che se si sta creando un sistema che si basa su tale comportamento, chiamare GC.Collect () è un buon modo per verificare tale codice.




2
using(var stream = new MemoryStream())
{
   bitmap.Save(stream, ImageFormat.Png);
   techObject.Last().Image = Image.FromStream(stream);
   bitmap.Dispose();

   // Without this code, I had an OutOfMemory exception.
   GC.Collect();
   GC.WaitForPendingFinalizers();
   //
}

2

Ci sono alcune situazioni in cui è meglio prevenire che curare.

Ecco una situazione.

È possibile creare una DLL non gestita in C # usando riscritture IL (perché ci sono situazioni in cui ciò è necessario).

Supponiamo ora, ad esempio, che la DLL crei una matrice di byte a livello di classe, poiché molte delle funzioni esportate hanno bisogno di accedervi. Cosa succede quando la DLL viene scaricata? Il garbage collector viene chiamato automaticamente a quel punto? Non lo so, ma essendo una DLL non gestita è del tutto possibile che il GC non venga chiamato. E sarebbe un grosso problema se non fosse chiamato. Quando la DLL viene scaricata, lo sarebbe anche il garbage collector, quindi chi sarà responsabile della raccolta di eventuali rifiuti e come lo farebbero? Meglio impiegare il garbage collector di C #. Avere una funzione di pulizia (disponibile per il client DLL) in cui le variabili a livello di classe sono impostate su null e viene chiamato il garbage collector.

Meglio prevenire che curare.


2

non sono ancora abbastanza sicuro di questo. Lavoro da 7 anni su un Application Server. Le nostre installazioni più grandi utilizzano Ram da 24 GB. È fortemente multithread e TUTTE le chiamate per GC.Collect () hanno riscontrato problemi di prestazioni davvero terribili.

Molti componenti di terze parti hanno utilizzato GC.Collect () quando hanno pensato che fosse intelligente farlo proprio ora. Quindi un semplice gruppo di Excel-Reports ha bloccato l'App Server per tutti i thread più volte al minuto.

Abbiamo dovuto riformattare tutti i componenti di terze parti per rimuovere le chiamate GC.Collect () e tutti hanno funzionato bene dopo averlo fatto.

Ma sto eseguendo Server anche su Win32, e qui ho iniziato a fare un uso intensivo di GC.Collect () dopo aver ottenuto OutOfMemoryException.

Ma non sono abbastanza sicuro di questo, perché ho notato spesso, quando ottengo una OOM a 32 bit, e riprovo a eseguire di nuovo la stessa Operazione, senza chiamare GC.Collect (), ha funzionato bene.

Una cosa che mi chiedo è l'eccezione OOM stessa ... Se avessi scritto .Net Framework e non potessi allocare un blocco di memoria, userei GC.Collect (), deframmenta memoria (??), riprova e se ancora non riesco a trovare un blocco di memoria libero, allora lancerei l'eccezione OOM.

O almeno rendere questo comportamento come opzione configurabile, a causa degli svantaggi del problema di prestazioni con GC.Collect.

Ora ho un sacco di codice come questo nella mia app per "risolvere" il problema:

public static TResult ExecuteOOMAware<T1, T2, TResult>(Func<T1,T2 ,TResult> func, T1 a1, T2 a2)
{

    int oomCounter = 0;
    int maxOOMRetries = 10;
    do
    {
        try
        {
            return func(a1, a2);
        }
        catch (OutOfMemoryException)
        {
            oomCounter++;
            if (maxOOMRetries > 10)
            {
                throw;
            }
            else
            {
                Log.Info("OutOfMemory-Exception caught, Trying to fix. Counter: " + oomCounter.ToString());
                System.Threading.Thread.Sleep(TimeSpan.FromSeconds(oomCounter * 10));
                GC.Collect();
            }
        }
    } while (oomCounter < maxOOMRetries);

    // never gets hitted.
    return default(TResult);
}

(Si noti che il comportamento Thread.Sleep () è un comportamento davvero app-app, perché stiamo eseguendo un servizio di memorizzazione nella cache ORM e il servizio impiega del tempo per rilasciare tutti gli oggetti memorizzati nella cache, se la RAM supera alcuni valori predefiniti. pochi secondi la prima volta e ha aumentato il tempo di attesa ogni occorrenza di OOM.)


Un componente non deve chiamare GC.Collect. Poiché ha un effetto a livello di applicazione, solo l'applicazione dovrebbe farlo (se non del tutto).
Codici A Caos il

If i would have written the .Net Framework, and i can't alloc a memory block, i would use GC.Collect(),- Penso che lo stiano già facendo - Ho visto le indicazioni che uno dei trigger GC interni sono alcuni errori nell'allocazione della memoria.
G. Stoynev,

2

Dovresti cercare di evitare di usare GC.Collect () poiché è molto costoso. Ecco un esempio:

        public void ClearFrame(ulong timeStamp)
    {
        if (RecordSet.Count <= 0) return;
        if (Limit == false)
        {
            var seconds = (timeStamp - RecordSet[0].TimeStamp)/1000;
            if (seconds <= _preFramesTime) return;
            Limit = true;
            do
            {
                RecordSet.Remove(RecordSet[0]);
            } while (((timeStamp - RecordSet[0].TimeStamp) / 1000) > _preFramesTime);
        }
        else
        {
            RecordSet.Remove(RecordSet[0]);

        }
        GC.Collect(); // AVOID
    }

RISULTATO DELLA PROVA: UTILIZZO DELLA CPU 12%

Quando si passa a questo:

        public void ClearFrame(ulong timeStamp)
    {
        if (RecordSet.Count <= 0) return;
        if (Limit == false)
        {
            var seconds = (timeStamp - RecordSet[0].TimeStamp)/1000;
            if (seconds <= _preFramesTime) return;
            Limit = true;
            do
            {
                RecordSet[0].Dispose(); //  Bitmap destroyed!
                RecordSet.Remove(RecordSet[0]);
            } while (((timeStamp - RecordSet[0].TimeStamp) / 1000) > _preFramesTime);
        }
        else
        {
            RecordSet[0].Dispose(); //  Bitmap destroyed!
            RecordSet.Remove(RecordSet[0]);

        }
        //GC.Collect();
    }

RISULTATO DELLA PROVA: UTILIZZO DELLA CPU 2-3%


1

Un altro motivo è quando un SerialPort è aperto su una porta COM USB e quindi il dispositivo USB è scollegato. Poiché SerialPort è stato aperto, la risorsa contiene un riferimento alla porta precedentemente connessa nel registro di sistema. Il registro di sistema conterrà quindi dati non aggiornati , quindi l'elenco delle porte disponibili sarà errato. Pertanto la porta deve essere chiusa.

La chiamata a SerialPort.Close () sulla porta chiama Dispose () sull'oggetto, ma rimane in memoria fino a quando non viene effettivamente eseguita la garbage collection, facendo sì che il registro rimanga stantio fino a quando il garbage collector non decida di rilasciare la risorsa.

Da https://stackoverflow.com/a/58810699/8685342 :

try
{
    if (port != null)
        port.Close(); //this will throw an exception if the port was unplugged
}
catch (Exception ex) //of type 'System.IO.IOException'
{
    System.GC.Collect();
    System.GC.WaitForPendingFinalizers();
}

port = null;

0

Questo non è rilevante per la domanda, ma per le trasformazioni XSLT in .NET (XSLCompiledTranform), potresti non avere scelta. Un altro candidato è il controllo MSHTML.



0

un buon motivo per chiamare GC è su piccoli computer ARM con poca memoria, come il Raspberry PI (in esecuzione con mono). Se i frammenti di memoria non allocati utilizzano troppa RAM di sistema, il sistema operativo Linux può diventare instabile. Ho un'applicazione in cui devo chiamare GC ogni secondo (!) Per eliminare i problemi di overflow della memoria.

Un'altra buona soluzione è quella di disporre gli oggetti quando non sono più necessari. Purtroppo questo non è così facile in molti casi.


0

Poiché ci sono Small object heap (SOH) e Large object heap (LOH)

Possiamo chiamare GC.Collect () per cancellare l'oggetto de-reference in SOP e spostare l'oggetto vissuto alla generazione successiva.

In .net4.5, possiamo anche compattare LOH usando la modalità compatta largeobjectheap


0

Se stai creando molti nuovi System.Drawing.Bitmapoggetti, Garbage Collector non li cancella. Alla fine GDI + penserà che stai esaurendo la memoria e genererà un'eccezione "Il parametro non è valido". Chiamare GC.Collect()ogni tanto (non troppo spesso!) Sembra risolvere questo problema.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.