Laden...

Wie gebe ich den Speicher bei Arrays oder Listen wieder frei?

Erstellt von BerndFfm vor 3 Jahren Letzter Beitrag vor 3 Jahren 1.380 Views
BerndFfm Themenstarter:in
3.825 Beiträge seit 2006
vor 3 Jahren
Wie gebe ich den Speicher bei Arrays oder Listen wieder frei?

Hallo,

ich bin jetzt auf einen Fehler in meinen Applikationen gestoßen nachdem 2 Kunden öfter einen Memory Overflow hatten. Alle Protokolle haben nichts ausgesagt.

Jetzt habe ich festgestellt dass es durch Arrays / Lists kommt deren Speicher nicht freigegeben wird.

Hintergrund : Vor langer Zeit habe ich nach einer Möglichkeit gesucht um Bildschirmlisten mit großen Datenmengen sehr schnell anzuzeigen. Etwas Fertiges gab es damals nicht, deshalb habe ich das wie folgt selber gebaut :

Virtuelles ListView, es werden die ersten 30 Zeilen angezeigt, dann wird "Fertig" vorgegaukelt. In einem eigenen Thread wird im Hintergrund ein Array mit den Items gefüllt. Die Zeilen werden nur hergestellt wenn man tatsächlich hinblättert (Lazy Loading).
So wird eine Liste mit über eine Millionen Datensätzen in einer Sekunde angezeigt.
Nur wenn man nach einer Spalte sortiert dauert es länger.

Die ListViewItems speichere ich in einem Array, das ist der Klasse als private definiert ist.

private ListViewItem[] adr;

private void InitList()
{
    adr = new ListViewItem[anzzeilen];
    Thread t = new Thread(ArrayFuellen);
    t.Start();
}

Auch nach Beenden der Klasse und Dispose bleibt der Speicher durch das Array belegt. Auch wenn der Thread beendet wird.

Auch ein Umbau auf List<> ergibt keine Änderung.

Alle Bordmittel von .NET schlagen fehl, der Speicher bleibt auf jeden Fall belegt.

Übrigens : Ist .NET 3.5.

Welche Möglichkeiten gibt es den Speichern freizugeben ?

Oder mehrere ListViewItems so zu speichern dass der Speicher wieder freigegeben wird ?

Grüße Bernd

Workshop : Datenbanken mit ADO.NET
Xamarin Mobile App : Finderwille Einsatz App
Unternehmenssoftware : Quasar-3

4.931 Beiträge seit 2008
vor 3 Jahren

Ich denke, das liegt nicht am Array, sondern an den einzelnen ListViewItems. Sind diese noch der ListView zugeordnet (oder anderweitig als Referenz)?

Ansonsten könntest du auch ja mal den Memory-Profiler laufen lassen und schauen, welche Objekte nach vermeintlicher Freigabe noch existieren.

2.078 Beiträge seit 2012
vor 3 Jahren

Wenn es noch irgendwo eine Referenz auf dieses Array gibt, wird der Speicher nie freigegeben.

Also suche, wo es noch Referenzen gibt und sorge dafür, dass die nicht erhalten bleiben, wenn aufgeräumt werden soll.
Events sind da ein beliebter Fehler, denn die sorgen zwangsläufig dazu, dass deine Instanz (die wiederum das Array hat) bei der Instanz, die das Event pflegt, referenziert bleibt.

Auch nach Beenden der Klasse und Dispose bleibt der Speicher durch das Array belegt

Eine Klasse kann nicht "beendet" werden. Solange noch Referenzen existieren, räumt der GC nicht auf, völlig egal, was Du in deinem Dispose (was ja auch nur eine dumme Methode ist) tust.
Du könntest im Dispose die Variable mit dem Array auf null setzen, das funktioniert aber nur, wenn es keine weiteren Referenzen gibt. Außerdem ist das vermutlich noch nicht das eigentliche Problem, denn irgendetwas hält eine Referenz auf das Objekt, wo das Array drin ist und diese Stelle musst Du finden.

Welche Möglichkeiten gibt es den Speichern freizugeben ?

Alle Referenzen entfernen, danach räumt der GC von alleine auf.
Wenn der GC nicht aufräumt, gibt's noch Referenzen.

PS:
Und Th69 hat natürlich Recht: Es kann alles noch an der ListView hängen, dann hast Du damit auch eine Referenz, die Du aufräumen musst.
Da sollte es aber reichen, die Liste mit den ListItems zu leeren und/oder die DataSource auf null zu setzen.

BerndFfm Themenstarter:in
3.825 Beiträge seit 2006
vor 3 Jahren

Meiner Meinung nach sind alle Referenzen entfernt.

Aber das ist ein guter Hinweis, werde ich mal genau untersuchen.

Grüße Bernd

Workshop : Datenbanken mit ADO.NET
Xamarin Mobile App : Finderwille Einsatz App
Unternehmenssoftware : Quasar-3

6.911 Beiträge seit 2009
vor 3 Jahren

Hallo BerndFfm,

die Ursache / Lösung der anderen ist die sauberste Art und Weise das zu handhaben.
Tipp: VS Performance Profiler -> .NET Object Allocation Tracking, dort kannst du die "Wurzeln" betrachten.

Sonst ev. auch Weak References.

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!"

BerndFfm Themenstarter:in
3.825 Beiträge seit 2006
vor 3 Jahren

Nein, noch nicht.

Habe erstmal Quick and Dirty eine Meldung ins Programm eingebaut dass man das Programm neu starten soll wenn mehr als 1000 MB belegt sind.

Ich werde berichten.

Grüße Bernd

Workshop : Datenbanken mit ADO.NET
Xamarin Mobile App : Finderwille Einsatz App
Unternehmenssoftware : Quasar-3

S
248 Beiträge seit 2008
vor 3 Jahren

Hallo BerndFfm,

handelt es sich um WPF oder um Forms?

Grüße

BerndFfm Themenstarter:in
3.825 Beiträge seit 2006
vor 3 Jahren

Die Anzeige erfolgt mit einem ListView im VirtualMode.

Das ist WinForms.

Grüße Bernd

Workshop : Datenbanken mit ADO.NET
Xamarin Mobile App : Finderwille Einsatz App
Unternehmenssoftware : Quasar-3

5.657 Beiträge seit 2006
vor 3 Jahren

Bist Du weitergekommen ?

Was wird in ArrayFuellen() gemacht ? Ein Event abonniert ?

Grüße MrSparkle

Weeks of programming can save you hours of planning

BerndFfm Themenstarter:in
3.825 Beiträge seit 2006
vor 3 Jahren

In ArrayFuellen wird nichts besonderes gemacht, auch keine Events abonniert.

Ich muss das mal genau analysieren wenn ich mehr Zeit habe.

Ich werde berichten.

Grüße Bernd

...
	Liste.VirtualMode = true;
	Liste.RetrieveVirtualItem += new System.Windows.Forms.RetrieveVirtualItemEventHandler(Liste_RetrieveVirtualItem);
	Liste.VirtualListSize = anzzeilen;
...

private ArrayFuellen()
{
    int m = 0;
    foreach(row ...)
    {
        ListViewItem lvItem = new ListViewItem(inhalt);
        for (int i = 1; i < anzspal; i++)
        {
            lvItem.SubItems.Add(...);
        }
        adr[m] = lvItem;
        m++;
    }
}
						
private void Liste_RetrieveVirtualItem(object sender, RetrieveVirtualItemEventArgs e)
{
    if (adr[e.ItemIndex] != null)
        e.Item = adr[e.ItemIndex];
    else
    {
        e.Item = new ListViewItem("");
        for (int i = 1; i < anzspal; i++) e.Item.SubItems.Add("");
    }
}

Workshop : Datenbanken mit ADO.NET
Xamarin Mobile App : Finderwille Einsatz App
Unternehmenssoftware : Quasar-3

4.931 Beiträge seit 2008
vor 3 Jahren

Zweite Nachfrage, da es mich sehr interessiert - gibt es Updates deinerseits?

J
641 Beiträge seit 2007
vor 3 Jahren
Virtualisierung...

Noch als Info, für die Virtualiserung (hatte das früher auch mal in WPF gebraucht), hatte ich damals das eingesetzt https://github.com/dotnetprojects/VirtualCollection (stammt nicht von mir, hab es nur geforkt und ein paar dinge gefixt)

cSharp Projekte : https://github.com/jogibear9988