Quali strategie e strumenti sono utili per trovare perdite di memoria in .NET?


152

Ho scritto C ++ per 10 anni. Ho riscontrato problemi di memoria, ma potrebbero essere risolti con un ragionevole sforzo.

Negli ultimi due anni ho scritto C #. Trovo di avere ancora molti problemi di memoria. Sono difficili da diagnosticare e risolvere a causa della non determinanza e perché la filosofia C # è che non dovresti preoccuparti di queste cose quando lo fai decisamente.

Un problema particolare che riscontro è che devo smaltire e ripulire esplicitamente tutto nel codice. Se non lo faccio, i profiler della memoria non aiutano davvero perché c'è così tanta pula che fluttua attorno a te che non riesci a trovare una perdita tra tutti i dati che stanno cercando di mostrarti. Mi chiedo se ho un'idea sbagliata o se lo strumento che ho non è il migliore.

Che tipo di strategie e strumenti sono utili per affrontare le perdite di memoria in .NET?


Il titolo del tuo post non corrisponde davvero alla domanda nel tuo post. Ti suggerisco di aggiornare il tuo titolo.
Kevin,

Hai ragione. Mi dispiace, mi stancavo un po 'dell'attuale perdita che sto cacciando! Titolo aggiornato.
Scott Langham,

3
@Scott: non essere stufo di .NET, non è un problema. Il tuo codice è.
GEOCHET,

3
Sì, il mio codice o le librerie di terze parti che ho il piacere di usare.
Scott Langham,

@Scott: vedi la mia risposta. Ne vale la pena MemProfiler. Usarlo ti darà anche un livello completamente nuovo di comprensione del mondo GC GC.
GEOCHET,

Risposte:


51

Uso il MemProfiler di Scitech quando sospetto una perdita di memoria.

Finora l'ho trovato molto affidabile e potente. Mi ha salvato la pancetta in almeno un'occasione.

Il GC funziona molto bene in .NET IMO, ma proprio come qualsiasi altra lingua o piattaforma, se scrivi codice errato, possono succedere cose brutte.


3
Sì, ci ho provato con questo, e mi ha aiutato ad arrivare al fondo di alcune perdite difficili. Le maggiori perdite che avevo scoperto erano causate da librerie di terze parti in codice non gestito a cui accedevano tramite l'interoperabilità. Sono rimasto colpito dal fatto che questo strumento ha rilevato perdite nel codice non gestito e nel codice gestito.
Scott Langham,

1
Ho accettato questa come risposta perché è quello che ha funzionato per me alla fine, ma penso che tutte le altre risposte siano molto utili. A proposito, questo strumento è più comunemente chiamato Mem Profiler di SciTech!
Scott Langham,

41

Solo per il problema dell'oblio da smaltire, prova la soluzione descritta in questo post del blog . Ecco l'essenza:

    public void Dispose ()
    {
        // Dispose logic here ...

        // It's a bad error if someone forgets to call Dispose,
        // so in Debug builds, we put a finalizer in to detect
        // the error. If Dispose is called, we suppress the
        // finalizer.
#if DEBUG
        GC.SuppressFinalize(this);
#endif
    }

#if DEBUG
    ~TimedLock()
    {
        // If this finalizer runs, someone somewhere failed to
        // call Dispose, which means we've failed to leave
        // a monitor!
        System.Diagnostics.Debug.Fail("Undisposed lock");
    }
#endif

Preferirei lanciare un'eccezione invece di Debug.Fail
Pedro77

17

Nel nostro progetto abbiamo utilizzato il software Ants Profiler Pro di Red Gate. Funziona davvero bene per tutte le applicazioni basate sul linguaggio .NET.

Abbiamo scoperto che .NET Garbage Collector è molto "sicuro" nella pulizia degli oggetti in memoria (come dovrebbe essere). Manterrebbe gli oggetti solo perché potremmo usarli in futuro. Ciò significava che dovevamo stare più attenti al numero di oggetti che abbiamo gonfiato in memoria. Alla fine, abbiamo convertito tutti i nostri oggetti dati in un "gonfiaggio su richiesta" (poco prima che venga richiesto un campo) al fine di ridurre il sovraccarico di memoria e aumentare le prestazioni.

EDIT: Ecco un'ulteriore spiegazione di cosa intendo per "gonfiare su richiesta". Nel nostro modello a oggetti del nostro database utilizziamo Proprietà di un oggetto padre per esporre gli oggetti figlio. Ad esempio, se avessimo un record che faceva riferimento ad altri record "dettaglio" o "ricerca" su base uno a uno, lo struttureremmo così:

class ParentObject
   Private mRelatedObject as New CRelatedObject
   public Readonly property RelatedObject() as CRelatedObject
      get
         mRelatedObject.getWithID(RelatedObjectID)
         return mRelatedObject
      end get
   end property
End class

Abbiamo scoperto che il sistema sopra ha creato alcuni problemi di memoria e prestazioni reali quando c'erano molti record in memoria. Quindi siamo passati a un sistema in cui gli oggetti venivano gonfiati solo quando venivano richiesti e le chiamate al database venivano eseguite solo quando necessario:

class ParentObject
   Private mRelatedObject as CRelatedObject
   Public ReadOnly Property RelatedObject() as CRelatedObject
      Get
         If mRelatedObject is Nothing
            mRelatedObject = New CRelatedObject
         End If
         If mRelatedObject.isEmptyObject
            mRelatedObject.getWithID(RelatedObjectID)
         End If
         return mRelatedObject
      end get
   end Property
end class

Ciò si è rivelato molto più efficiente perché gli oggetti sono stati tenuti fuori memoria fino a quando non sono stati necessari (è stato effettuato l'accesso al metodo Get). Ha fornito un notevole incremento delle prestazioni nel limitare gli accessi al database e un enorme guadagno nello spazio di memoria.


Secondo questo prodotto. È stato uno dei migliori profiler che ho usato.
Gord,

Ho trovato il profiler abbastanza buono per esaminare i problemi di prestazioni. Tuttavia, gli strumenti di analisi della memoria erano piuttosto scadenti. Ho trovato una perdita con questo strumento, ma è stato spazzatura aiutarmi a identificare la causa della perdita. E non ti aiuta affatto se la perdita si trova nel codice non gestito.
Scott Langham,

Ok, la nuova versione 5.1 è molto meglio. È meglio aiutarti a trovare la causa della perdita (anche se - ci sono ancora un paio di problemi che ANTS mi ha detto che risolveranno nella prossima versione). Tuttavia non esegue ancora il codice non gestito, ma se non ti preoccupi del codice non gestito, questo è ora uno strumento abbastanza buono.
Scott Langham,

7

È ancora necessario preoccuparsi della memoria quando si scrive codice gestito a meno che l'applicazione non sia banale. Suggerirò due cose: in primo luogo, leggere CLR via C # perché ti aiuterà a capire la gestione della memoria in .NET. In secondo luogo, impara a utilizzare uno strumento come CLRProfiler (Microsoft). Questo può darti un'idea di ciò che sta causando la tua perdita di memoria (ad esempio puoi dare un'occhiata alla frammentazione dell'heap di oggetti di grandi dimensioni)


Sì. CLRPRofiler è piuttosto interessante. Può diventare un po 'esplosivo con le informazioni quando si tenta di scavare nella vista che ti dà degli oggetti allocati, ma tutto è lì. È sicuramente un buon punto di partenza, soprattutto perché è gratuito.
Scott Langham,

6

Stai usando un codice non gestito? Se non si utilizza codice non gestito, secondo Microsoft, le perdite di memoria in senso tradizionale non sono possibili.

Tuttavia, la memoria utilizzata da un'applicazione potrebbe non essere rilasciata, pertanto l'allocazione della memoria di un'applicazione potrebbe aumentare nel corso della vita dell'applicazione.

Da Come identificare le perdite di memoria in Common Language Runtime su Microsoft.com

Una perdita di memoria può verificarsi in un'applicazione .NET Framework quando si utilizza codice non gestito come parte dell'applicazione. Questo codice non gestito può perdere la memoria e il runtime di .NET Framework non può risolvere questo problema.

Inoltre, un progetto può sembrare solo avere una perdita di memoria. Questa condizione può verificarsi se molti oggetti di grandi dimensioni (come gli oggetti DataTable) vengono dichiarati e quindi aggiunti a una raccolta (come un DataSet). Le risorse possedute da questi oggetti potrebbero non essere mai rilasciate e le risorse rimarranno vive per l'intera esecuzione del programma. Questa sembra essere una perdita, ma in realtà è solo un sintomo del modo in cui la memoria viene allocata nel programma.

Per gestire questo tipo di problema, è possibile implementare IDisposable . Se vuoi vedere alcune delle strategie per gestire la gestione della memoria, suggerirei di cercare IDisposable, XNA, gestione della memoria poiché gli sviluppatori di giochi devono avere una garbage collection più prevedibile e quindi costringere il GC a fare le sue cose.

Un errore comune è quello di non rimuovere i gestori di eventi che si iscrivono a un oggetto. Una sottoscrizione del gestore eventi impedisce il riciclo di un oggetto. Inoltre, dai un'occhiata all'istruzione using che ti consente di creare un ambito limitato per la durata di una risorsa.


5
Vedi blogs.msdn.com/tess/archive/2006/01/23/… . Non importa se la perdita di memoria è "tradizionale" o meno, è comunque una perdita.
Constantin,

2
Vedo il tuo punto, ma l'allocazione e il riutilizzo della memoria inefficienti da parte di un programma sono diversi da una perdita di memoria.
Timothy Lee Russell,

buona risposta, grazie per avermi ricordato che i gestori di eventi possono essere pericolosi.
Frameworkninja,

3
@Timothy Lee Russel: se una quantità illimitata (1) di memoria può rimanere allocata (radicata) contemporaneamente dopo essere diventata inutile (2), senza che nulla nel sistema abbia le informazioni e l'impulso necessarie per sradicarle in modo tempestivo, è una perdita di memoria . Anche se un giorno la memoria potesse essere liberata, se abbastanza cose inutili si accumulassero per soffocare il sistema prima che ciò accada, è una perdita. (1) Maggiore di O (N), N è la quantità di allocazione utile; (2) Roba è inutile se la rimozione di riferimenti ad essa non influirebbe sulla funzionalità del programma.
supercat

2
@Timothy Lee Russel: Il normale modello di "perdita di memoria" si verifica quando la memoria è trattenuta da un'entità per conto di un'altra entità , aspettandosi di essere informata quando non è più necessaria, ma quest'ultima abbandona l'entità senza dirla alla prima. L'entità che contiene la memoria non ne ha davvero bisogno, ma non c'è modo di determinarlo.
supercat

5

Questo blog ha alcune fantastiche procedure dettagliate che utilizzano windbg e altri strumenti per rintracciare le perdite di memoria di tutti i tipi. Ottima lettura per sviluppare le tue abilità.


5

Ho appena avuto una perdita di memoria in un servizio di Windows, che ho risolto.

Innanzitutto, ho provato MemProfiler . Ho trovato davvero difficile da usare e per niente user friendly.

Quindi, ho usato JustTrace che è più facile da usare e ti dà maggiori dettagli sugli oggetti che non sono disposti correttamente.

Mi ha permesso di risolvere la perdita di memoria molto facilmente.


3

Se le perdite che stai osservando sono dovute a un'implementazione della cache in fuga, questo è uno scenario in cui potresti voler considerare l'uso di WeakReference. Ciò potrebbe aiutare a garantire che la memoria venga rilasciata quando necessario.

Tuttavia, IMHO sarebbe meglio prendere in considerazione una soluzione su misura - solo tu sai davvero per quanto tempo devi tenere gli oggetti in giro, quindi progettare un codice di pulizia adeguato per la tua situazione è di solito l'approccio migliore.


3

Preferisco dotmemory di Jetbrains


potresti essere l'unico :)
HellBaby,

L'ho provato anche io. Penso che questo sia un buon strumento. Facile da usare, informativo. Si integra con Visual Studio
occhi rossi del

Nel nostro caso, durante la risoluzione dei problemi di perdita di memoria, lo strumento Snapshot di Visual Studio si è bloccato / non è stato snapshot. Dotmemory ha mantenuto le sue istantanee multiple e gestite da 3+ GB con (apparentemente) facilità.
Michael Kargl,

3

Big guns - Strumenti di debug per Windows

Questa è una straordinaria collezione di strumenti. Puoi analizzare sia heap gestiti che non gestiti con esso e puoi farlo offline. Questo è stato molto utile per il debug di una delle nostre applicazioni ASP.NET che ha continuato a riciclare a causa di un uso eccessivo della memoria. Ho dovuto solo creare un dump della memoria completa del processo di vita in esecuzione sul server di produzione, tutte le analisi sono state eseguite offline in WinDbg. (Si è scoperto che alcuni sviluppatori stavano abusando dell'archiviazione di sessioni in memoria.)

"Se rotto è ..." blog ha articoli molto utili sull'argomento.


2

La cosa migliore da tenere a mente è tenere traccia dei riferimenti ai tuoi oggetti. È molto facile finire con riferimenti sospesi ad oggetti che non ti interessano più. Se non hai più intenzione di utilizzare qualcosa, eliminalo.

Abituati a utilizzare un provider di cache con scadenze scorrevoli, in modo che se qualcosa non viene referenziato per l'intervallo di tempo desiderato, viene negato e ripulito. Ma se si accede molto, lo dirà in memoria.


2

Uno dei migliori strumenti è utilizzare gli strumenti di debug per Windows e eseguire un dump della memoria del processo utilizzando adplus , quindi utilizzare windbg e il plug-in sos per analizzare la memoria del processo, i thread e gli stack di chiamate.

È possibile utilizzare questo metodo anche per identificare i problemi sui server, dopo aver installato gli strumenti, condividere la directory, quindi connettersi alla condivisione dal server utilizzando (net use) e prendere un arresto anomalo o interrompere il dump del processo.

Quindi analizza offline.


Sì, funziona bene, specialmente per cose più avanzate o per diagnosticare problemi nel software rilasciato a cui non è possibile collegare facilmente un debugger. Questo blog offre molti suggerimenti sull'uso corretto di questi strumenti: blogs.msdn.com/tess
Scott Langham,

2

Dopo una delle mie correzioni per l'applicazione gestita ho avuto la stessa cosa, ad esempio come verificare che la mia applicazione non abbia la stessa perdita di memoria dopo la mia prossima modifica, quindi ho scritto qualcosa come Framework di verifica rilascio oggetto, per favore dai un'occhiata il pacchetto NuGet ObjectReleaseVerification . Puoi trovare un esempio qui https://github.com/outcoldman/OutcoldSolutions-ObjectReleaseVerification-Sample e informazioni su questo esempio http://outcoldman.ru/en/blog/show/322


0

Da Visual Studio 2015 prendere in considerazione l'utilizzo dello strumento diagnostico di utilizzo immediato della memoria per raccogliere e analizzare i dati di utilizzo della memoria.

Lo strumento di utilizzo della memoria consente di acquisire una o più istantanee dell'heap di memoria nativa e gestita per comprendere l'impatto sull'utilizzo della memoria dei tipi di oggetti.


0

uno dei migliori strumenti che ho usato con DotMemory. puoi usare questo strumento come estensione in VS. dopo aver eseguito la tua app puoi analizzare ogni parte della memoria (per Object, NameSpace, ecc.) che la tua app usa e fare qualche foto , Confrontalo con altri SnapShots. DotMemory

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.