8 Stimmen

Wo ist das Speicherleck in dieser Funktion?

Bearbeiten2: Ich möchte nur sicherstellen, dass meine Frage klar ist: Warum, bei jeder Iteration von AppendToLog(), die Anwendung 15mb mehr verwendet? (die Größe der ursprünglichen Protokolldatei)

Ich habe eine Funktion namens AppendToLog(), die den Dateipfad eines HTML-Dokuments empfängt, etwas analysiert und an eine Datei anhängt. Sie wird auf diese Weise aufgerufen:

this.user_email = uemail;
string wanted_user = wemail;

string[] logPaths;
logPaths = this.getLogPaths(wanted_user);

foreach (string path in logPaths)
{              

    this.AppendToLog(path);                

}

Bei jeder Iteration steigt der RAM-Verbrauch um etwa 15 MB. Dies ist die Funktion: (sieht lang aus, ist aber einfach)

public void AppendToLog(string path)
{

Encoding enc = Encoding.GetEncoding("ISO-8859-2");
StringBuilder fb = new StringBuilder();
FileStream sourcef;
string[] messages;

try
{
    sourcef = new FileStream(path, FileMode.Open);
}
catch (IOException)
{
    throw new IOException("The chat log is in use by another process."); ;
}
using (StreamReader sreader = new StreamReader(sourcef, enc))
{

    string file_buffer;
    while ((file_buffer = sreader.ReadLine()) != null)
    {
        fb.Append(file_buffer);
    }                
}

//Array of each line's content
messages = parseMessages(fb.ToString());

fb = null;

string destFileName = String.Format("{0}_log.txt",System.IO.Path.GetFileNameWithoutExtension(path));
FileStream destf = new FileStream(destFileName, FileMode.Append);
using (StreamWriter swriter = new StreamWriter(destf, enc))
{
    foreach (string message in messages)
    {
        if (message != null)
        {
            swriter.WriteLine(message);
        }
    }
}

messages = null;

sourcef.Dispose();
destf.Dispose();

sourcef = null;
destf = null;
}

Ich habe seit Tagen damit zu tun und weiß nicht, was ich tun soll :(

Bearbeiten: Dies ist ParseMessages, eine Funktion, die HtmlAgilityPack verwendet, um Teile eines HTML-Protokolls zu entfernen.

public string[] parseMessages(string what)
{
StringBuilder sb = new StringBuilder();
HtmlDocument doc = new HtmlDocument();

doc.LoadHtml(what);            

HtmlNodeCollection messageGroups = doc.DocumentNode.SelectNodes("//body/div[@class='mplsession']");
int messageCount = doc.DocumentNode.SelectNodes("//tbody/tr").Count;

doc = null;

string[] buffer = new string[messageCount];

int i = 0;

foreach (HtmlNode sessiongroup in messageGroups)
{
    HtmlNode tablegroup = sessiongroup.SelectSingleNode("table/tbody");

    string sessiontime = sessiongroup.Attributes["id"].Value;

    HtmlNodeCollection messages = tablegroup.SelectNodes("tr");
    if (messages != null)
    {
        foreach (HtmlNode htmlNode in messages)
        {
            sb.Append(
                    ParseMessageDate(
                        sessiontime,
                        htmlNode.ChildNodes[0].ChildNodes[0].InnerText
                    )
                ); //Date
            sb.Append(" ");

            try
            {
                foreach (HtmlTextNode node in htmlNode.ChildNodes[0].SelectNodes("text()"))
                {
                    sb.Append(node.Text.Trim()); //Name
                }
            }
            catch (NullReferenceException)
            {
                /*
                 * We ignore this exception, it just means there's extra text
                 * and that means that it's not a normal message
                 * but a system message instead
                 * (i.e. "John logged off")
                 * Therefore we add the "::" mark for future organizing
                 */
                sb.Append("::");
            }
            sb.Append(" ");

            string message = htmlNode.ChildNodes[1].InnerHtml;
            message = message.Replace(""", "'");
            message = message.Replace(" ", " ");
            message = RemoveMedia(message);
            sb.Append(message); //Message
            buffer[i] = sb.ToString();
            sb = new StringBuilder();
            i++;
        }
    }
}
messageGroups = null;
what = null;
return buffer;
}

0 Stimmen

Sie brauchen keine FileStream wenn Sie eventuell eine StreamReader . Sehen Sie sich die Konstrukteure an.

7voto

Kevin Brock Punkte 8659

Wie viele bereits erwähnt haben, ist dies wahrscheinlich nur ein Artefakt der GC nicht bereinigt den Speicher Speicher so schnell wie Sie erwarten, dass es. Dies ist normal für verwaltete Sprachen, wie C#, Java, etc. Sie müssen wirklich herausfinden, ob der Ihrem Programm zugewiesene Speicher frei ist oder nicht, wenn Sie an dieser Nutzung interessiert sind. Die Fragen, die Sie sich in diesem Zusammenhang stellen sollten, sind:

  1. Wie lange läuft Ihr Programm? Handelt es sich um ein Dienstprogramm, das ständig läuft?
  2. Wird während der Ausführung weiterhin Speicher vom Betriebssystem zugewiesen oder wird ein stabiler Zustand erreicht? (Haben Sie es lange genug laufen lassen, um das herauszufinden?)

Ihr Code sieht nicht so aus, als würde er ein "Speicherleck" aufweisen. In verwalteten Sprachen gibt es eigentlich keine Speicherlecks wie in C/C++ (es sei denn, Sie verwenden unsicher oder externe Bibliotheken, die C/C++ sind). Allerdings müssen Sie auf Referenzen achten, die in der Nähe bleiben oder versteckt sind (z. B. eine Collection-Klasse, der gesagt wurde, dass sie ein Element entfernen soll, die aber das Element des internen Arrays nicht auf null ). Im Allgemeinen können Objekte mit Verweisen auf dem Stack (Locals und Parameter) nicht "auslaufen", es sei denn, Sie speichern den Verweis auf das/die Objekt(e) in einer Objekt-/Klassenvariablen.

Einige Anmerkungen zu Ihrem Code:

  1. Sie können die Zuweisung/Deallokation von Speicher verringern, indem Sie die StringBuilder mindestens auf die richtige Größe bringen. Da Sie wissen, dass Sie die gesamte Datei im Speicher halten müssen, ordnen Sie ihn der Dateigröße zu (dadurch erhalten Sie einen Puffer, der etwas größer ist als erforderlich, da Sie keine Zeilenumbrüche speichern, die Datei aber wahrscheinlich welche hat):

    FileInfo fi = new FileInfo(path);
    StringBuilder fb = new StringBuilder((int) fi.Length);

    Sie können sicherstellen, dass die Datei existiert, bevor Sie ihre Länge ermitteln, indem Sie fi um dies zu überprüfen. Beachten Sie, dass ich gerade die Länge auf eine int ohne Fehlerprüfung, da Ihre Dateien weniger als 2 GB groß sind, wie aus dem Text Ihrer Frage hervorgeht. Wenn das nicht der Fall ist, dann sollten Sie die Länge vor dem Casting überprüfen, vielleicht eine Ausnahme auslösen, wenn die Datei zu groß ist.

  2. Ich würde empfehlen, die gesamte variable = null Anweisungen in Ihrem Code. Diese sind nicht notwendig, da es sich um vom Stack zugewiesene Variablen handelt. Außerdem ist es in diesem Zusammenhang für die GC nicht hilfreich, da die Methode nicht lange leben wird. Mit diesen Anweisungen schaffen Sie also zusätzliches Durcheinander im Code und er ist schwieriger zu verstehen.

  3. In Ihrem ParseMessages Methode, fangen Sie eine NullReferenceException und nehmen an, dass es sich um einen Nicht-Text-Knoten handelt. Dies könnte in der Zukunft zu verwirrenden Problemen führen. Da dies etwas ist, von dem Sie erwarten, dass es normalerweise passiert als Ergebnis von etwas, das in den Daten vorhanden sein kann sollten Sie die Bedingung im Code überprüfen, z. B.:

    if (node.Text != null)
        sb.Append(node.Text.Trim()); //Name

    Ausnahmen sind für außergewöhnliche/unerwartete Bedingungen im Code vorgesehen. Die Zuweisung einer signifikanten Bedeutung an NullReferenceException mehr als nur eine Nullreferenz war, kann (und wird wahrscheinlich) Fehler in anderen Teilen desselben try jetzt oder bei künftigen Änderungen blockieren.

4voto

RossFabricant Punkte 11872

Es gibt kein Speicherleck. Wenn Sie den Windows Task-Manager verwenden, um den von Ihrer .NET-Anwendung verwendeten Speicher zu messen, erhalten Sie kein klares Bild davon, was vor sich geht, da der GC den Speicher auf eine komplexe Weise verwaltet, die der Task-Manager nicht wiedergibt.

Ein MS-Ingenieur schrieb einen großartigen Artikel darüber, warum .NET-Anwendungen, die scheinbar Speicherplatz verlieren, es wahrscheinlich nicht tun, und es enthält Links zu sehr ausführlichen Erklärungen, wie die GC tatsächlich funktioniert. Jeder .NET-Programmierer sollte sie lesen.

2voto

Ash Punkte 58914

Ich würde mir genau ansehen, warum Sie eine Zeichenkette an parseMessages übergeben müssen, d.h. fb.ToString().

Ihr Code-Kommentar besagt, dass dies ein Array mit dem Inhalt jeder Zeile zurückgibt. Sie lesen jedoch tatsächlich alle Zeilen aus der Protokolldatei in fb und konvertieren sie dann in eine Zeichenkette.

Wenn Sie große Dateien in parseMessages() parsen, können Sie dies viel effizienter tun, indem Sie den StringBuilder selbst oder den StreamReader an parseMessages() übergeben. Dies würde es ermöglichen, immer nur einen Teil der Datei in den Speicher zu laden, im Gegensatz zur Verwendung von ToString(), die derzeit die gesamte Logdatei in den Speicher zwingt.

Die Wahrscheinlichkeit, dass in einer .NET-Anwendung ein echtes Speicherleck auftritt, ist dank der Garbage Collection geringer. Sie scheinen keine großen Ressourcen wie Dateien zu verwenden, so dass es noch unwahrscheinlicher ist, dass Sie ein tatsächliches Speicherleck haben.

Es sieht so aus, als hätten Sie Ressourcen entsorgt, aber der GC hat wahrscheinlich Schwierigkeiten, die großen Speicherblöcke rechtzeitig zuzuweisen und wieder freizugeben, bevor die nächste Iteration beginnt, und so sehen Sie den steigenden Speicherverbrauch.

Auch wenn GC.Collect() es Ihnen ermöglicht, die Freigabe von Speicher zu erzwingen, würde ich dringend empfehlen, die oben genannten Vorschläge zu prüfen, bevor Sie versuchen, den Speicher manuell über GC zu verwalten.

[Update] Sehen Sie Ihre parseMessages() und die Verwendung von HtmlAgilityPack (eine sehr nützliche Bibliothek, nebenbei bemerkt) sieht es wahrscheinlich gibt einige große und möglicherweise zahlreiche Zuweisungen von Speicher für jede Logile durchgeführt werden.

HtmlAgility weist intern Speicher für verschiedene Knoten zu, in Kombination mit Ihrem Pufferarray und den Zuweisungen in der Hauptfunktion bin ich sogar noch zuversichtlicher, dass die GC unter großen Druck gesetzt wird, um Schritt zu halten.

Um mit dem Raten aufzuhören und einige echte Metriken zu erhalten, würde ich Folgendes ausführen ProzessExplorer und fügen Sie die Spalten für die GC Gen 0,1,2 Sammlungen hinzu. Führen Sie dann Ihre Anwendung aus und beobachten Sie die Anzahl der Sammlungen. Wenn Sie große Zahlen in diesen Spalten sehen, hat die GC Probleme und Sie sollten das Design überarbeiten, um weniger Speicherzuweisungen zu verwenden.

Alternativ kann auch die kostenlose CLR Profiler 2.0 von Microsoft bietet eine schöne visuelle Darstellung von .NET-Speicherzuweisungen innerhalb Ihrer Anwendung.

1voto

Gregory Punkte 501

Eine Sache, die Sie vielleicht ausprobieren möchten, ist die vorübergehende Erzwingung einer GC.Collect nach jedem Lauf. Die GC ist sehr intelligent und wird erst dann Speicher zurückfordern, wenn sie der Meinung ist, dass die Kosten für ein Collect den Wert des zurückgewonnenen Speichers wert sind.

Edit: Ich wollte nur hinzufügen, dass es wichtig ist, zu verstehen, dass der manuelle Aufruf von GC.Collect eine schlechte Praxis ist (für jeden normalen Anwendungsfall. Abnormal == vielleicht eine Ladefunktion für ein Spiel oder ähnliches). Man sollte den Garbage Collector entscheiden lassen, was das Beste ist, da er in der Regel mehr Informationen über Systemressourcen und ähnliches hat, auf die er sein Sammelverhalten stützen kann, als man selbst.

1voto

Hamish Grubijan Punkte 10258

Der try-catch-Block könnte ein finally (Aufräumen) gebrauchen. Wenn man sich ansieht, was die using-Anweisung tut, ist sie äquivalent zu try catch finally. Ja, GC auszuführen ist auch eine gute Idee. Ohne diesen Code zu kompilieren und auszuprobieren, ist es schwer, das mit Sicherheit zu sagen ...

Entsorgen Sie den Kerl außerdem ordnungsgemäß mit einem Müllsack:

FileStream destf = new FileStream(destFileName, FileMode.Append);

Effektives C# 2. Auflage nachschlagen

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X