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

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 ancora molti problemi di memoria. Sono difficili da diagnosticare e correggere a causa della non-determinatezza, e poiché la filosofia C # è che non dovresti preoccuparti di cose del genere quando lo fai davvero.

Un problema particolare che trovo è che devo disporre e pulire esplicitamente tutto nel codice. Se non lo faccio, allora i profiler della memoria non sono di grande aiuto perché c’è così tanto rumore che non riesci a trovare una perdita all’interno di 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?

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

Finora, ho trovato che sia molto affidabile e potente. Ha salvato il bacon in almeno un’occasione.

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

Solo per il problema di dimenticare-to-dispose, 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 

Nel nostro progetto abbiamo utilizzato Ants Profiler Pro di Red Gate. Funziona molto bene per tutte le applicazioni basate su linguaggio .NET.

Abbiamo rilevato che .NET Garbage Collector è molto “sicuro” nella sua pulizia degli oggetti in memoria (come dovrebbe essere). Manterrebbe gli oggetti in giro solo perché potremmo utilizzarli in futuro. Ciò significava che dovevamo essere più attenti al numero di oggetti che abbiamo gonfiato in memoria. Alla fine, abbiamo convertito tutti i nostri oggetti dati in un “gonfiamento su richiesta” (appena prima che venga richiesto un campo) per ridurre il sovraccarico della 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 le proprietà di un object genitore per esporre gli oggetti figli. Ad esempio, se avessimo un record che faceva riferimento ad altri record “detail” o “lookup” su base uno a uno, lo struttureremmo in questo modo:

 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 di cui sopra ha creato 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 venivano tenuti fuori dalla memoria finché non erano necessari (si accede al metodo Get). Ha fornito un notevole incremento delle prestazioni nel limitare gli accessi al database e un enorme guadagno nello spazio di memoria.

È ancora necessario preoccuparsi della memoria quando si scrive codice gestito a meno che l’applicazione non sia banale. Suggerirò due cose: innanzitutto, leggi CLR tramite 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 cosa sta causando la tua perdita di memoria (ad esempio puoi dare un’occhiata alla frammentazione del tuo heap di oggetti di grandi dimensioni)

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

La memoria utilizzata da un’applicazione potrebbe tuttavia non essere rilasciata, quindi l’allocazione di memoria di un’applicazione potrebbe aumentare durante la vita dell’applicazione.

Da Come identificare le perdite di memoria nel 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 memoria e il runtime di .NET Framework non può risolvere questo problema.

Inoltre, è ansible che solo un progetto abbia una perdita di memoria. Questa condizione può verificarsi se molti oggetti di grandi dimensioni (come 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 vengono lasciate in vita per l’intera esecuzione del programma. Questo 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, è ansible implementare IDisposable . Se vuoi vedere alcune delle strategie per gestire la gestione della memoria, ti suggerisco di cercare la gestione della memoria IDisposable, XNA, in quanto gli sviluppatori di giochi devono disporre di una garbage collection più prevedibile e quindi devono forzare il GC a fare le sue cose.

Un errore comune è non rimuovere i gestori di eventi che si iscrivono a un object. Un abbonamento al gestore di eventi impedirà il riciclo di un object. Inoltre, dai un’occhiata all’istruzione using che ti consente di creare un ambito limitato per la vita di una risorsa.

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

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

Innanzitutto, ho provato MemProfiler . L’ho trovato davvero difficile da usare e per niente facile da usare.

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

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

Se le perdite che stai osservando sono dovute all’implementazione di una cache in fuga, questo è uno scenario in cui potresti prendere in considerazione l’utilizzo di WeakReference. Ciò potrebbe aiutare a garantire che la memoria venga rilasciata quando necessario.

Tuttavia, IMHO sarebbe meglio considerare una soluzione su misura – solo tu sai davvero quanto tempo hai bisogno per mantenere gli oggetti in giro, quindi la progettazione di un codice di manutenzione appropriato per la tua situazione è solitamente l’approccio migliore.

La cosa migliore da tenere a mente è di tenere traccia dei riferimenti ai tuoi oggetti. È molto facile finire con appesi riferimenti a oggetti che non ti interessano più. Se non userai più qualcosa, sbarazzati di esso.

Abituarsi a utilizzare un provider di cache con scadenze scorrevoli, in modo che se non viene fatto riferimento a qualcosa per un intervallo di tempo desiderato viene cancellato e cancellato. Ma se si accede molto si dirà in memoria.

Uno degli strumenti migliori è l’utilizzo degli strumenti di debug per Windows e l’acquisizione di 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.

È ansible 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 (utilizzo netto) e prendere un crash o eseguire il dump del processo.

Quindi analizzare offline.

Big guns: strumenti di debug per Windows

Questa è una straordinaria collezione di strumenti. Puoi analizzare sia gli heap gestiti che quelli non gestiti 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 dell’uso eccessivo della memoria. Ho solo dovuto creare un dump di memoria completo del processo di vita in esecuzione sul server di produzione, tutte le analisi sono state eseguite offline in WinDbg. (È emerso che alcuni sviluppatori stavano sfruttando eccessivamente la memoria Session in memoria).

“Se rotto è …” il blog ha articoli molto utili sull’argomento.

Dopo una delle mie correzioni per l’applicazione gestita ho avuto la stessa cosa, come verificare che la mia applicazione non abbia la stessa perdita di memoria dopo la mia prossima modifica, quindi ho scritto qualcosa come Object Release Verification framework, per favore guarda 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

In Visual Studio (versioni Premium o Enterprise), è ansible aprire il dump dell’heap e eseguire il debug dell’heap gestito. Quando fai clic su un tipo, le tabs sottostanti ti permetteranno di vedere quali altri tipi fanno riferimento a quegli oggetti.

È ansible utilizzare PerfView per un’analisi più dettagliata:

  1. Nel menu Memoria, selezionare Take Heap Snapshot.

  2. Nella finestra di dialogo visualizzata, seleziona il processo da analizzare e fai clic su Scarica heap GC. Puoi opzionalmente bloccare il processo o forzare un GC a verificarsi prima di raccogliere l’istantanea.

  3. Al termine dell’istantanea, fai clic su Chiudi.

Visual Studio e PerfView sono principalmente utili per l’analisi aggregata. PerfView è un profiler di campionamento, anche quando analizza l’heap, quindi a volte fornisce un’immagine distorta dell’aspetto dell’heap. Se è necessario eseguire il drill down su un object specifico o ottenere l’assoluta verità sull’intera immagine, è necessario iniziare a utilizzare il debugger o il CLR MD.

Preferisco dotmemory di Jetbrains

Da Visual Studio 2015 è consigliabile utilizzare uno strumento diagnostico di utilizzo memoria pronto all’uso per raccogliere e analizzare i dati sull’utilizzo della memoria.

Lo strumento Utilizzo memoria consente di acquisire una o più istantanee dell’heap della memoria gestito e nativo per aiutare a comprendere l’impatto sull’utilizzo della memoria dei tipi di object.