2010-04-29 15 views
14

Recentemente ho esaminato alcune "perdite di memoria" di .NET (ovvero oggetti radicati di GC inattesi e persistenti) in un'app WinForms. Dopo aver caricato e chiuso un rapporto enorme, l'utilizzo della memoria non è diminuito come previsto anche dopo un paio di raccolte di gen2. Supponendo che il controllo dei rapporti fosse mantenuto in vita da un gestore di eventi randagio ho aperto WinDbg per vedere cosa stava succedendo ...Ricerca .NET "Perdita di memoria"

Utilizzando WinDbg, il comando !dumpheap -stat riportava che una grande quantità di memoria veniva consumata dalle istanze di stringa. Ulteriore affinamento questo giù con il comando !dumpheap -type System.String ho trovato il colpevole, una stringa 90MB utilizzato per il rapporto, all'indirizzo 03be7930. L'ultimo passaggio è stato invocare !gcroot 03be7930 per vedere quali oggetti lo mantenevano in vita.

Le mie aspettative non erano corrette: non era un gestore eventi sganciato appeso al controllo dei rapporti (e alla stringa del rapporto), ma era trattenuto da un'istanza System.Text.RegularExpressions.RegexInterpreter, che a sua volta è un discendente di un System.Text.RegularExpressions.CachedCodeEntry. Ora, la memorizzazione nella cache di regexs è (un po ') conoscenza comune come questo aiuta a ridurre il sovraccarico di dover ricompilare il Regex ogni volta che viene utilizzato. Ma cosa c'entra questo con il tenere in vita la mia stringa?

In base all'analisi mediante Reflector, risulta che la stringa di input è memorizzata in RegexInterpreter ogni volta che viene chiamato un metodo Regex. RegexInterpreter mantiene questo riferimento di stringa finché una nuova stringa non viene inserita da una successiva chiamata al metodo Regex. Mi aspetto un comportamento simile aggrappandomi alle istanze di Regex.Match e forse ad altre. La catena è qualcosa di simile:

  • Regex.Split, Regex.Match, Regex.Replace, ecc
    • Regex.Run
      • RegexScanner.Scan (RegexScanner è la classe base, RegexInterpreter è la sottoclasse sopra descritta).

L'incriminato Regex viene utilizzato solo per la segnalazione, usato raramente, e quindi improbabile da utilizzare di nuovo per cancellare la stringa di report esistente. E anche se il Regex fosse usato in un secondo momento, probabilmente avrebbe elaborato un altro report di grandi dimensioni. Questo è un problema relativamente significativo e sembra semplicemente sporco.

Detto questo, ho trovato alcune opzioni su come risolvere, o almeno aggirare, questo scenario. Farò in modo che la community risponda per prima e se nessuno si avvicina, colmerò le eventuali lacune in un giorno o due.

+1

Si sta utilizzando l'opzione 'Compiled' quando si crea il Regex? –

+0

No, l'opzione 'Compiled' non è stata utilizzata in questo caso. –

risposta

8

Stai usando istanze di Regex o metodi statici Regex che prendono un modello di stringa? According to this post, le istanze Regex non partecipano alla memorizzazione nella cache.

+2

Sì, l'uso dei metodi statici Regex è stato il colpevole.È possibile verificare che la memorizzazione nella cache sia utilizzata dai metodi statici tramite Reflector: tutte le chiamate statiche creano un Regex utilizzando il codificatore privato che accetta il parametro 'useCache'. La soluzione semplice qui è di non usare i metodi statici. Il caching non è critico perché la compilazione è banale rispetto all'elaborazione delle stringhe di input enormi. Altre soluzioni che possono essere utili, a seconda di come viene utilizzato il Regex, è di disabilitare il caching Regex impostando Regex.CacheSize su 0 o eseguendo una stringa vuota attraverso il Regex dopo aver elaborato l'origine. –

0

Prova a passare a un Regex compilato - l'istanza richiede più tempo, ma forse non sarà soggetta a questa perdita dispari.

Vedere http://msdn.microsoft.com/en-us/library/system.text.regularexpressions.regexoptions%28v=VS.100%29.aspx per ulteriori informazioni.

Oppure, non trattenere l'istanza di Regex più del necessario: creare uno nuovo per ogni richiamo di report.

+2

Downvoted, perché [la compilazione dell'espressione regolare SEMPRE perde memoria] (http://blog.codinghorror.com/to-compile-or-not-to-compile/), in base alla progettazione (degli assembly non è scaricabili a meno che non si scarichi l'intero AppDomain): 'Ci sono ancora più costi per la compilazione che dovrebbero essere menzionati, tuttavia. Emettere IL con Reflection.Emit carica un sacco di codice e utilizza molta memoria, e non è la memoria a cui tornerai mai. A meno che non lo fai in un AppDomain da buttare via, che porta un sacco di nuove sfide , ad esempio, prestazioni di inter-AppDomain scadenti, ad esempio, rispetto alle prestazioni in-AppDomain. –