2010-04-29 18 views
14

Ich habe vor kurzem einige .NET "Speicherlecks" (d. H. Unerwartete, verweilende GC-Root-Objekte) in einer WinForms App untersucht. Nach dem Laden und Schließen eines umfangreichen Berichts ist die Speicherbelegung auch nach einigen gen2-Sammlungen nicht wie erwartet gesunken. Unter der Annahme, dass die Berichterstattung Kontrolle wurde von einer verirrten Ereignishandler lebendig gehalten werden ich offen WinDbg geknackt, um zu sehen, was los war ....NET RegEx "Memory Leak" Untersuchung

Mit WinDbg, der !dumpheap -stat Befehl berichtet eine große Menge an Speicher, der von String-Instanzen verbraucht wurde. Weiter verfeinernd dies mit dem !dumpheap -type System.String Befehl Ich fand den Übeltäter, eine 90MB-Zeichenfolge für den Bericht, unter der Adresse 03be7930. Der letzte Schritt war, !gcroot 03be7930 aufzurufen, um zu sehen, welches Objekt (e) es am Leben erhalten hat.

Meine Erwartungen waren falsch - es war kein unhooked Event-Handler, der am Reporting-Steuerelement (und der Berichtszeichenfolge) hängen blieb, sondern stattdessen von einer System.Text.RegularExpressions.RegexInterpreter Instanz, die selbst ein Nachkomme eines System.Text.RegularExpressions.CachedCodeEntry ist. Nun ist das Caching von Regexs (etwas) allgemein bekannt, da dies hilft, den Aufwand zu reduzieren, den Regex bei jeder Verwendung neu zu kompilieren. Aber was hat das damit zu tun, meine Saite am Leben zu erhalten?

Basierend auf der Analyse mithilfe von Reflector stellt sich heraus, dass die Eingabezeichenfolge im RegexInterpreter immer dann gespeichert wird, wenn eine Regex-Methode aufgerufen wird. Der RegexInterpreter behält diese Zeichenfolge-Referenz bei, bis eine neue Zeichenfolge durch einen nachfolgenden Regex-Methodenaufruf in diese Zeichenfolge eingegeben wird. Ich würde ein ähnliches Verhalten erwarten, wenn ich an Regex.Match-Instanzen und vielleicht auch andere hängen würde. Die Kette ist so etwas wie dieses:

  • Regex.Split, Regex.Match, Regex.Replace, etc
    • Regex.Run
      • RegexScanner.Scan (RegexScanner ist die Basisklasse, RegexInterpreter ist die oben beschriebene Unterklasse).

Die fehlbare Regex nur für die Berichterstattung verwendet wird, nur selten verwendet, und daher unwahrscheinlich, wieder verwendet werden, um den vorhandenen Bericht Zeichenfolge zu löschen. Und selbst wenn die Regex zu einem späteren Zeitpunkt verwendet würde, würde sie wahrscheinlich einen weiteren großen Bericht verarbeiten. Dies ist ein relativ bedeutendes Problem und fühlt sich einfach schmutzig an.

Alles, was ich gesagt habe, habe ich ein paar Optionen gefunden, wie man dieses Szenario lösen oder zumindest umgehen kann. Ich lasse die Gemeinde zuerst antworten und wenn keine Teilnehmer kommen, werde ich die Lücken in ein oder zwei Tagen füllen.

+1

Verwenden Sie die Option 'Compiled', wenn Sie den Regex erstellen? –

+0

Nein, die Option 'Compiled' wurde in diesem Fall nicht verwendet. –

Antwort

8

Verwenden Sie Instanzen von Regex oder die statischen Regex-Methoden, die ein Zeichenfolgenmuster verwenden? According to this post, Regex-Instanzen nehmen nicht am Caching teil.

+2

Ja, die Verwendung von statischen Regex-Methoden war der Schuldige.Sie können überprüfen, ob Caching von den statischen Methoden über Reflector verwendet wird - alle statischen Aufrufe erstellen einen Regex mit dem privaten Schlüssel, der den Parameter 'useCache' verwendet. Die einfache Lösung hier ist, die statischen Methoden nicht zu verwenden. Caching ist nicht kritisch, da die Kompilierung verglichen mit der Verarbeitung der großen Eingabezeichenfolgen trivial ist. Andere Lösungen, die nützlich sein können, je nachdem, wie die Regex verwendet wird, ist das Regex-Caching zu deaktivieren, indem Sie Regex.CacheSize auf 0 setzen oder eine leere Zeichenfolge durch die Regex ausführen, nachdem die Quelle verarbeitet wurde. –

0

Versuchen Sie, zu einem kompilierten Regex zu wechseln - die Instanziierung dauert länger, wird aber möglicherweise nicht diesem seltsamen Leck unterliegen.

Weitere Informationen finden Sie unter http://msdn.microsoft.com/en-us/library/system.text.regularexpressions.regexoptions%28v=VS.100%29.aspx.

Oder halten Sie die Regex-Instanz nicht länger als nötig - erstellen Sie eine neue für jeden Berichtsaufruf.

+2

Downvoted, da [kompilierender regulärer Ausdruck IMMER Speicher verliert] (http://blog.codinghorror.com/to-compile-or-not-to-compile/), von Entwurf (von Assemblys, die nicht entladen werden können, wenn das Ganze nicht entladen wird AppDomain): 'Es gibt sogar noch mehr Kosten für die Kompilierung, die erwähnt werden sollten. Emulation von IL mit Reflection.Emit lädt eine Menge Code und verwendet viel Speicher, und das ist kein Speicher, den du jemals zurückbekommst. Es sei denn, du machst das in einer Wegwerf-AppDomain, die eine ganze Reihe neuer Herausforderungen mit sich bringt , wie zum Beispiel eine schlechte Leistung zwischen den Anwendungsdomänen, verglichen mit der Leistung in der Anwendungsdomäne. –