Speicherleck
-
Hallo Leute,
ich habe in meiner Anwendung da Problem, dass diese immer mehr Speicher beansprucht. So nach ca. 5 Wochen Dauerlauf ist der Ofen aus, das System bleibt sehen.
Ich habe lange im Internet nach passenden Antworten gesucht, bin aber noch nicht so richtig fündig geworden.
Mit dem PerfMon habe ich mal die folgenden Werte überwacht:
1 Prozess/Private Bytes
2 .NET CLR-Speicher/Anzahl der Bytes in den Heaps
3 .NET CLR-Sperren und -Threads/Anzahl der aktuellen logischen Threads.
4 Auflistungsanzahl der Generation 0
5 Auflistungsanzahl der Generation 1Ich habe festgstellt, dass die Werte von Punkt 4 und 5 kontinuirlich ab Programmstart wachsen.
Also habe ich mal einen Test mit einem ganz einfachen Programm gemacht. Ich öffne mittels Button und StreamReader eine Datei und weise die Zeile aus dem StreamReader einem String zu. Danach wird der StreamReader wieder geschlossen.
Hier der kurze Code:
private: System::Void buttonSTartTest_Click(System::Object^ sender, System::EventArgs^ e) { String ^tempString=""; StreamReader ^sReader=gcnew StreamReader("C:\\Test.log"); while(sReader->EndOfStream!=true){ tempString=sReader->ReadLine(); } sReader->Close(); }
Ist nicht besonders sinnvoll, ich weiß, aber es geht ja um das daraus resultierende Problem.
Wenn ich jetzt den Button so einige Male anklicke wächst der Wert von "Auflistungsanzahl der Generation 0" mit jedem Klick und fällt nicht wieder zurück.
Das kann doch so nicht richtig sein, oder?
-
Schonmal das aufräumen des GC erzwungen?
GC::Collect();
Ich weiß nun auch im Moment nicht, ob der String^ auf dem Heap landet?
Ich meine aber, Ja.
-
Hey Fred.,
ja, leider ja, es hilft nicht.
sReader->~StreamReader();
und/oder
den StreamReader als Attribute der Form hat auch keine Besserung gebracht.Ich habe auch versucht, den StreamReader zusammen mit einem FileStream zu öffnen. War aber ebenso Erfolglos.
Allein auch ganz ohne String ^, man mg es nicht glauben, stellt das gleiche Problem dar.
-
Um das auch gesagt zu haben.
Der Wert von Gen 0 fällt erst zurück, wenn die Anwendung insgesamt geschlossen wird.
-
Wenn ich jetzt den Button so einige Male anklicke wächst der Wert von "Auflistungsanzahl der Generation 0" mit jedem Klick und fällt nicht wieder zurück.
Das kann doch so nicht richtig sein, oder?
Doch das kann es sehr wohl!
Es kann gut sein, dass es bei Dir ein Memory Leak gibt, jedoch ist es nicht so einfach wie Du das hier darstellst (in dem Bsp. Code). Auch stelle ich hier mal in Frage, ob "So nach ca. 5 Wochen Dauerlauf ist der Ofen aus, das System bleibt sehen." als Fehlerbeschreibung reichen.
Könnte auch ein anderes Resource Leak sein (Handles z.B.)... also gibts entsprechede Exceptions oder wie äussert sich das Problem?Fred . schrieb:
Schonmal das aufräumen des GC erzwungen?
Das ist ein sehr schlechte Variante. Besonders wenn man nicht genau weiss was man tut. Behebe besser den Fehler und lerne wie der GC arbeitet.
Fred . schrieb:
Ich weiß nun auch im Moment nicht, ob der String^ auf dem Heap landet?
Ich meine aber, Ja.Das spielt auch keine Rolle!
Hier schliesslich noch ein alter Post von mir zu dem Thema:
Es ist wichtig, dass Du weisst, wann Objekte vom GC freigegeben werden können.
Lies Dich in das Thema ein.- Ein berühmter Kandidat für Memory Leaks unter .NET ist das event- Handling.
- Ein zweiter Kandidat wäre eine fehlender Aufruf der Endxxx(..) Methoden bei asynchronen Operation (durch Beginxxx(..) gestartet). Hier gehts um Handle Leaks.Benutze Tools um die Memory Leaks zu finden.
Z.B. VS2005 od. VS2008 (ab Team System)
http://www.microsoft.com/germany/msdn/vstudio/products/produktvergleich.mspxOd. den DevPartner von Compuware...
Hier sind noch ein wertvoller Anhaltspunkt:
http://msdn2.microsoft.com/en-us/library/ms954591.aspxGruss Simon
Auch auf Codeproject gibts massig Artikel zum Thema Resource (Memory) Leaks unter .NET.
Zusammengefasst: Aus dem was Du schreibst lässt sich für mich nicht schliessen, dass Du ein Memory Leak hast. Finde heraus ob das tatsächlich das Problem ist und dann informiere dich.
Simon
-
Es kommt dazu, dass andere Anwendungen nach ca. 5 Wochen nicht mehr fähig sind, bestimmte Schritte immerhalb einer bestimmten Zeit durchzuführen, bis eine Applictaion stehen bleibt oder einen Kommunikationsfehler aufweist.
Meine Application hat sich dann schon mal mit dem Fehler gemeldet:
System.IO.IOException: Nicht genügend Quoten verfügbar, um diesen Befehl zu verarbeiten.Simon, Du schreibst, das der Wert wachsen darf.
Ich verstehe mal daraus, dass das dann kein Problem ist, oder?
-
Simon, Du schreibst, das der Wert wachsen darf.
Ich verstehe mal daraus, dass das dann kein Problem ist, oder?Ja, korrekt.
-
Wenn ich dich richtig verstanden habe, dann zeigt sich das Problem durch die geworfene IOException. Richtig?
Wenn das so ist, wäre der Code, der die IOException auslöst hier von Nutzen.
Gruss Simon
-
Hallo Simon,
ja, es gibt gelegentlich diese Exception aber sie tritt nur undefiniert ein, die Funktion in der die Exception aufgetreten ist, wird bis zur Exception unngefähr 30 - 40 mal aufgerufen, oder sogar noch viel öffter.
Meint, ich könnte nicht sicher sagen wo das Problem genau herkommt, außerdem möchte ich den Code hier nicht vorzeigen. Das liegt vor allem daran, dass das ca. 1000 Zeilen sind.
Gelesen habe ich sehr viel im Netz, aber nichts hat mir so richtig geholfen.
Ich würde gerne den PerfMon verwenden (weil kostenfrei) um die Anwendung zu überprüfen.
Welche Werte muss ich mindestens für die Auswertung heranziehen?
-
Mit sos.dll kann man relativ gut Speicherlecks finden... noch besser sind graphische Tools die darauf aufbauen (z.B. .NET Memory Profiler):
http://memprofiler.com/Ich vermute, dass Du irgendwo ein Array hast wo Du nicht wieder freigeibst... oder Objekte am leben bleiben weil ein Delegate noch drauf lebt...
Dass der Speicher immer ansteigt ist eigentlich "normal". Dies ist bis zu einem gewissen Grad "normal". .NET nimmt sich so viel wie möglich/nötig. ABer wenn er immer ansteigt ist was faul... wenn er sich so bei 100 MB einpendelt ist es ok...
PS: Was soll den "Auflistungsanzahl in GC 0" sein (ich hasse die deutschen übersetzungen).
Interessanter wäre eher: "Speicher in GC0-2" und LOH
-
Ich habe mit Ants Profiler leider keine brauchbaren Ergebnisse erhalten. Aber ich werde Deinen Vorschlag die nächsten Tagen testen (memprofiler).
Mit dem PerfMon kann ich also nichts bewirken oder ermitteln?
Arrys lösche ich vorzeitig mit :
delete array
Kann man das auch für z.B. DateTime oder Srings machen?
Gibt
delete
den Spicher mit Sicherheit wieder frei?
-
In .NET löscht man nichts mit "delete"... sondern nur dadurch, dass man das Objekt nicht mehr verwendet und keine Verweise mehr darauf hat.
Ein "delete" auf managed objekte bewirkt entweder gar nichts oder es ruft "IDispose::Dispose()" auf.Mit dem Profiler bekommst Du nur ganz grob was raus. Mit einem FullDump und passenden Symbolen/EXE/DLLs (oft auch ohne Symbole) kannst Du hingegen einiges anfangen. Auch später noch, wenn Du die Dump-Datei in den MemProfiler lädst.
-
OK, danke erstmal für die Infos.
Ich mach mich dann mal an die MemProfiler.Happy new year!
-
Hallo Jochen,
ich habe zum MemProfiler fragen. Kennst Du Dich damit aus?
-
Frag halt!
-
Hallo Jochen,
hier nochmal zur Info, warum ich glabe, dass ich en Leck habe:
1. Andere Programme haben nach längerer Zeit (ca. 5 Wochen) Probleme und laufen nicht mehr richtig. Sie verursachen irgendwelche Fehler und stützen ab.
2. Man kann im Taskmanager über einen längeren Zeiraum hinweg beobachten, wie immer weniger virtueller Speicher zur Verfügung steht.Hauptsächlich laufen in meinem Formular 3 Timer.
Timer 1:
Hier für wird DateTime als Attribut im Formular angelegt, und im Konstruktor initialisiert.Im Event Timer - Tick wird dann im Sekundentakt die aktuelle Zeit ermittelt und in einem Label angezeigt. Im Timer wird also hauptsächlich das hier gemacht:
private: System::Void timer2_Tick(System::Object^ sender, System::EventArgs^ e) { label1->Text=dTime->Now.ToString(); }
Timer 2:
Hier wird lokal im Sekundentakt die Prozessliste ermittelt.private: System::Void timer1_Tick(System::Object^ sender, System::EventArgs^ e) { listBox1->Items->Clear(); array<Process^>^runningProcesses=Process::GetProcesses(); for each(Process ^element in runningProcesses) listBox1->Items->Add(element->ProcessName); }
Timer 3:
Öffnet im Sekunentakt eine Datei und durchsucht diese auf spezielle Vorkommen.private: System::Void timer3_Tick(System::Object^ sender, System::EventArgs^ e) { StreamReader ^sReader=gcnew StreamReader("C:\\testdatei.log");//nur eine Zeile String^zeile; if(sReader->EndOfStream!=true) zeile=sReader->ReadLine(); //Mach was mit der Zeile sReader->Close(); }
Wenn ich das jetzt mit dem MemProfiler starte und auswerte sehe ich im RealTime View folgendes:
Alein durch den Timer für die Processliste wachsen alle drei Heaps, 0 -2 kontinuierlich.
Das gleiche passiert auch bei dem Timer für die Zeit und für die Datei, als dem SreamReader.Warum fallen die Werte nicht zurück, sondern wandern immer weiter durch die Heaps durch?
Zumindest bez. dem Process Timer meint MemProfiler, dass zu viele "undisposed" Instanzen vorliegen.
Aber was kann ich dagegen tun?
Oder ist das alles gar kein Problem??
-
Wie hast Du die Timer angelegt?
Zeige auch diesen Code.Simon
-
Die Timer sind keine besonderen Threads.
Sie sind ganz einfach aus der Toolleiste in das Formular integriert.
Mittels Steuerelement also.
-
Ich weiss... solltest sie auch disposen.
Genau wie die Process Instanzen aus Process::GetProcesses().Simon
-
Hast Du mal versucht, jeweils nur einen Timer laufen zu lassen um das Problem einzugrenzen?
Simon