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).
- Regex.Run
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.
Si sta utilizzando l'opzione 'Compiled' quando si crea il Regex? –
No, l'opzione 'Compiled' non è stata utilizzata in questo caso. –