Willkommen auf myCSharp.de! Anmelden | kostenlos registrieren
 | Suche | FAQ

Hauptmenü
myCSharp.de
» Startseite
» Forum
» Suche
» Regeln
» Wie poste ich richtig?

Mitglieder
» Liste / Suche
» Wer ist online?

Ressourcen
» FAQ
» Artikel
» C#-Snippets
» Jobbörse
» Microsoft Docs

Team
» Kontakt
» Cookies
» Spenden
» Datenschutz
» Impressum

  • »
  • Community
  • |
  • Diskussionsforum
MemoryLeak "behebt" sich selbst durch Memory-Snapshot
Palladin007
myCSharp.de - Member

Avatar #avatar-4140.png


Dabei seit:
Beiträge: 1.761
Herkunft: Düsseldorf

Themenstarter:

MemoryLeak "behebt" sich selbst durch Memory-Snapshot

beantworten | zitieren | melden

Guten Mittag,

es geht um eine WPF-Anwendung.
Ich suche gerade ein MemoryLeak, der scheinbar nur auftritt, wenn es um sehr viele Daten geht, also auch die Auswirkungen sehr groß sind.

Dabei ist mir aufgefallen, dass ich mit Hilfe der Diagnostic Tools von Visual Studio den Leak problemlos reproduzieren kann (mehrere Instanzen einer Klasse, wo nur eine sein sollte), doch sobald ich mit dem .NET Memory Profiler einen Snapshot erstelle, sind die überflüssigen Instanzen weg und der Speicher freigegeben.
Eventuell hängt das mit großen zirkulären Referenzen zusammen? Heißt: Ein "Haupt"-Objekt referenziert sehr viele "Detail"-Objekte, die auf das "Haupt"-Objekt verweisen.

Ich kann nicht ganz sagen, ob es wirklich keine Referenzen mehr gibt, daher die Frage:
Kann es sein, dass in einem Fall mit sehr vielen Objekte im "Referenz-Kreis" der GC nicht hinterher kommt und einfach zu lange braucht?
Und wenn ja, wie gehe ich damit am besten um, kann ich den Fall prüfen und ggf. ausschließen?

Und ist es möglich, dem GC die Objekte, die ich definitiv nicht mehr brauche, mitzugeben und sie so "löschen" zu lassen?
private Nachricht | Beiträge des Benutzers
Th69
myCSharp.de - Experte

Avatar #avatar-2578.jpg


Dabei seit:
Beiträge: 4.320

beantworten | zitieren | melden

Kannst du nicht bei den "Detail"-Objekten die Referenz für das Hauptobjekt auf null setzen, sobald du das Hauptobjekt nicht mehr benötigst?
Dieser Beitrag wurde 1 mal editiert, zum letzten Mal von Th69 am .
private Nachricht | Beiträge des Benutzers
Palladin007
myCSharp.de - Member

Avatar #avatar-4140.png


Dabei seit:
Beiträge: 1.761
Herkunft: Düsseldorf

Themenstarter:

beantworten | zitieren | melden

Da bin ich gerade dran, ist aber nicht "mal eben so" gemacht, weil der Kreis etwas größer ist.

Ich hatte gehofft, man kann dem GC einen ganzen Objekt-Baum mitgeben, zumindest erinnere ich mich, mal etwas davon gelesen zu haben.
Oder das war ein Issue, wo jemand nach genau so einem Feature gefragt hat.
Oder ich werfe irgendetwas anderes ganz durcheinander


Ich teste das Mal und eine andere Theorie, dann melde ich mich wieder.
private Nachricht | Beiträge des Benutzers
Palladin007
myCSharp.de - Member

Avatar #avatar-4140.png


Dabei seit:
Beiträge: 1.761
Herkunft: Düsseldorf

Themenstarter:

beantworten | zitieren | melden

Zitat von Th69
Kannst du nicht bei den "Detail"-Objekten die Referenz für das Hauptobjekt auf null setzen, sobald du das Hauptobjekt nicht mehr benötigst?
Ergebnis: Es liegt sehr viel weniger im RAM, weil ich jetzt alles auf null gesetzt habe, was ich nur finden konnte, durch alle Klassen hindurch.
Die Instanzen, die nicht da sein sollten, gibt's allerdings immer noch und ein Snapshot vom .NET Memory Profiler "beseitigt" die Instanzen immer noch.

Meine zweite Theorie war scheinbar erfolgreicher, aber auch nur manchmal und ein Snapshot vom .NET Memory Profiler "behebt" das Problem immer noch.
Aber gut, das heißt wohl, dass das Problem doch irgendwo in meinem Code liegt
private Nachricht | Beiträge des Benutzers
Th69
myCSharp.de - Experte

Avatar #avatar-2578.jpg


Dabei seit:
Beiträge: 4.320

beantworten | zitieren | melden

Wenn durch den .NET Memory Profiler Speicher freigegeben wird, dann teste mal, ob auch ein GC.Collect (z.B. durch einen expliziten Button-Click) diesen freigibt.
Es wäre eigenartig, wenn es nicht so wäre.
Du darfst aber auch nicht erwarten, daß beim automatischen GC-Durchlauf jeder mögliche Speicher freigegeben wird, erst wenn der Speicher an die Grenzen stößt, wird mehr freigegeben (d.h. mehr Prozessorzeit dafür aufgebracht).
private Nachricht | Beiträge des Benutzers
gfoidl
myCSharp.de - Team

Avatar #avatar-2894.jpg


Dabei seit:
Beiträge: 6.814
Herkunft: Waidring

beantworten | zitieren | melden

Hallo Palladin007,
Zitat
.NET Memory Profiler einen Snapshot erstelle, sind die überflüssigen Instanzen weg und der Speicher freigegeben.
Die meisten dieser Profiler führen eine kompletten GC durch (also inkl. Gen-2 und LOH) und haken sich in den Informationsstrom vom GC rein um so an die Ojekte zu kommen.
Genau dieses Verhalten hast du beobachtet.

Versuch dich einmal an PerfView, das bietet i.d.R. mehr Infos / Möglichkeiten. Aber die UX ist sehr bescheiden...

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"
private Nachricht | Beiträge des Benutzers
Palladin007
myCSharp.de - Member

Avatar #avatar-4140.png


Dabei seit:
Beiträge: 1.761
Herkunft: Düsseldorf

Themenstarter:

beantworten | zitieren | melden

Zitat von gfoidl
Die meisten dieser Profiler führen eine kompletten GC durch (also inkl. Gen-2 und LOH) und haken sich in den Informationsstrom vom GC rein um so an die Ojekte zu kommen.
Genau dieses Verhalten hast du beobachtet.

Versuch dich einmal an PerfView, das bietet i.d.R. mehr Infos / Möglichkeiten. Aber die UX ist sehr bescheiden...
Das erklärt's ein bisschen, ich hab testweise nur ein einfaches GC.Collect gemacht, das geht vermutlich nicht weit genug?

Und danke für den Tipp - schau ich mir morgen an.
Zitat von Th69
Wenn durch den .NET Memory Profiler Speicher freigegeben wird, dann teste mal, ob auch ein GC.Collect (z.B. durch einen expliziten Button-Click) diesen freigibt.
Das hatte ich mir auch schon gedacht und GC.Collect eingefügt - allerdings nicht durch einen Button-Klick, sondern direkt nachdem alles weg geworfen wurde.
Gebracht hat es nichts, aber ich teste Morgen mal mit einem eigenen Button, vielleicht macht das einen Unterschied.
Zitat von Th69
erst wenn der Speicher an die Grenzen stößt, wird mehr freigegeben
Ich hätte nicht gedacht, dass er (in meinem Fall) knapp 3GB einfach so liegen lässt.
Aber gut, ich hab auch (noch) keine Ahnung, wie der genau arbeitet - das Buch liegt hier, aber die Zeit fehlt noch ^^
private Nachricht | Beiträge des Benutzers
Th69
myCSharp.de - Experte

Avatar #avatar-2578.jpg


Dabei seit:
Beiträge: 4.320

beantworten | zitieren | melden

Bei GC.Collect(Int32) kannst du die Generation einstellen, probiere mal GC.Collect(GC.MaxGeneration) (schreib auch mal, welchen Wert MaxGeneration bei dir hat).

PS: Überprüfe auch mal den Wert von GCSettings.LatencyMode (der sollte bei einem normalen Arbeitsplatzrechner auf Interactive stehen - und nicht auf einer der LowLatency-Werte).
Dieser Beitrag wurde 1 mal editiert, zum letzten Mal von Th69 am .
private Nachricht | Beiträge des Benutzers
Palladin007
myCSharp.de - Member

Avatar #avatar-4140.png


Dabei seit:
Beiträge: 1.761
Herkunft: Düsseldorf

Themenstarter:

beantworten | zitieren | melden

GC.MaxGeneration = 2
GCSettings.LatencyMode = Interactive

GC.Collect(GC.MaxGeneration) hat auch nichts gebracht.
Manchmal gibt er es auch von alleine frei, ich weiß allerdings nicht, wann und ob das die Daten vom aktuellen Dialog sind, den ich gerade verlasse, oder die von irgendwann vorher.
Wenn ich einen Button verwendet, um GC.Collect(GC.MaxGeneration) aufzurufen, gibt es meistens ungefähr die Menge frei, die ich erwarten würde.

Aber warum?

Ich hab jetzt folgendes eingebaut:


_ = Task.Delay(1000, default)
    .ContinueWith(_ => GC.Collect(GC.MaxGeneration), default(CancellationToken));
Das funktioniert so halb, es gibt ab dem zweiten Dialog-Verlassen wieder frei.

Ich schau mir jetzt PerfView an, vielleicht kann mir das ja etwas mehr verraten, vermutlich habe ich doch irgendwo ein gemeines Problem und ich jage hier unnötig dem GC hinterher.
private Nachricht | Beiträge des Benutzers
gfoidl
myCSharp.de - Team

Avatar #avatar-2894.jpg


Dabei seit:
Beiträge: 6.814
Herkunft: Waidring

beantworten | zitieren | melden

Hallo Palladin007,


GC.Collect();
GC.WaitForPendingFinalzers();
GC.Collect();
unter Angabe der max. Geneartion räumt den Speicher ordentlich auf.


mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"
private Nachricht | Beiträge des Benutzers
Palladin007
myCSharp.de - Member

Avatar #avatar-4140.png


Dabei seit:
Beiträge: 1.761
Herkunft: Düsseldorf

Themenstarter:

beantworten | zitieren | melden


GC.Collect(GC.MaxGeneration);
GC.WaitForPendingFinalizers();
GC.Collect(GC.MaxGeneration);
Warum GC.MaxGeneration? Einfach weil viel hilft viel
Ich will erst einmal sehen, ob es überhaupt etwas bringt, danach schaue ich mir an, ob GC.MaxGeneration zu viel ist oder nicht.

Aber tatsächlich hilft es, aber anders als erwartet.
Ich beobachte jetzt zwei Dinge:
  • Die RAM-Auslastung geht ca. eine Sekunde (ich hab's mit dem Delay eingebaut) nach dem Verlassen des Dialogs wieder runter
  • Die Kurve ist nach den ersten ein/zwei Mal Dialog anzeigen und verlassen sehr viel gleichmäßiger, wie als würden Objekte gecacht werden, was ich aber nicht mache

Gerader der zweite Punkt verwundert mich - hast Du dazu eine Erklärung?
Das GC gibt bei jedem Mal Verlassen des Dialogs sehr viel weniger frei, als ich erwarten würde, aber beim erneuten anzeigen, wird auch sehr viel weniger benötigt.
Könnte das mit SQLite zusammenhängen, dass der - ähnlich dem SQLServer - anfängt, die Daten im RAM vorzuhalten, um den Zugriff zu optimieren?

Aber so wie das aussieht, übernehme ich das für alle Dialoge, dass nach jedem Dialog verlassen erst einmal aufgeräumt wird.
Ich teste nur noch, ob auch ohne GC.MaxGeneration ausreicht und das Delay kann vermutlich auch raus.
Ob das eine gute Lösung ist, weiß ich nicht, aber sie hilft und da ich etwas unter Druck stehe, ist es zumindest eine vorübergehend geeignete Lösung - denke ich.


PS:
Mit PerfView hab ich ein paar Dumbs erstellt, nur werde ich daraus nicht schlau.
Bisher kannte ich das Tool nicht und es scheint auch einiges an Einarbeitung zu benötigen, doch die Zeit habe ich leider nicht.
Ich würde daher eine schnelle Lösung bevorzugen, auch wenn es nur ein Flickenteppich ist, aber dann kann ich es später nochmal angehen.
Dieser Beitrag wurde 1 mal editiert, zum letzten Mal von Palladin007 am .
private Nachricht | Beiträge des Benutzers
Palladin007
myCSharp.de - Member

Avatar #avatar-4140.png


Dabei seit:
Beiträge: 1.761
Herkunft: Düsseldorf

Themenstarter:

beantworten | zitieren | melden


private static async void TriggerDelayedFullGC()
{
    await Task.Delay(100, default).ConfigureAwait(false);

    GC.Collect();
    GC.WaitForPendingFinalizers();
    GC.Collect();
}

Das tut's auch.
So baue ich es erst einmal ein und schau später nochmal.
private Nachricht | Beiträge des Benutzers
gfoidl
myCSharp.de - Team

Avatar #avatar-2894.jpg


Dabei seit:
Beiträge: 6.814
Herkunft: Waidring

beantworten | zitieren | melden

Hallo Palladin007,

GC.Collect bitte nur als Notlösung betrachten, da dies die interne Arbeit und Tuning vom GC durcheinander bringt.

PerfView ist mächtig, aber nicht trivial und intuitiv.
Wenn du willst und es gestattet ist, so kann ich mir die Dumps einmal anschauen und dann eine grobe Art Anleitung dazu erstellen.
Aber ich muss bei PerfView auch immer wieder "reinkommen", da ich es recht schnell wieder vergesse ;-)

mfG Gü

PS: aber erst am Montag, übers WE bin ich nicht da.

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"
private Nachricht | Beiträge des Benutzers
Palladin007
myCSharp.de - Member

Avatar #avatar-4140.png


Dabei seit:
Beiträge: 1.761
Herkunft: Düsseldorf

Themenstarter:

beantworten | zitieren | melden

Zitat von gfoidl
GC.Collect
bitte nur als Notlösung betrachten, da dies die interne Arbeit und Tuning vom GC durcheinander bringt.
Leider sehe ich aktuell keine andere Option (außer das manuell alles auf null setzen), der Zeitplan ist schon lange überschritten und der Kunde hat auch nur noch diesen Monat
Da sind mir Notlösungen lieber, als 2,5 GB im RAM versauern zu lassen und es soll im Anschluss sowieso weiter gehen, dann aber mit ruhigerem Zeitplan.
Zitat von gfoidl
PerfView ist mächtig, aber nicht trivial und intuitiv.
Wenn du willst und es gestattet ist, so kann ich mir die Dumps einmal anschauen und dann eine grobe Art Anleitung dazu erstellen.
Aber ich muss bei PerfView auch immer wieder "reinkommen", da ich es recht schnell wieder vergesse ;-)
Danke für das Angebot, aber das kann ich nicht alleine entscheiden
Ich spreche das bei nächster Gelegenheit mal an.

Kann aber gut sein, dass sich das um ein paar Wochen (oder sogar Monate) verschiebt, wenn die Notlösung soweit ausreicht, auch da kann ich nur meine Meinung zu sagen, aber nichts entscheiden.
private Nachricht | Beiträge des Benutzers