Große Problematiken mit "System.OutOfMemoryException" in der Anwendung

  • VB.NET

Es gibt 14 Antworten in diesem Thema. Der letzte Beitrag () ist von Bibi.

    Große Problematiken mit "System.OutOfMemoryException" in der Anwendung

    Hallo,

    ich Entwickele nun schon seit einer längeren Zeit an einem Tool, das diverse Aufgaben in unserer Firma für uns erledigt.
    In erster Linie handelt es sich dabei um die Datenverarbeitung.
    Es werden Daten von div. Schnittstellen (Datenbankzugriffe über MySQL, CSV, XML und so weiter) importiert, verarbeitet und exportiert.

    Die Routinen an Sich funktionieren dabei wunderbar und es gab bis vor Kurzem keine Probleme.
    Da ich nun auf meiner Entwicklungsumgebung auf ein 64Bit System umsteigen musste, musste ich auch auf VB.NET 2010 umsteigen, womit ich das Programm weiterhin in 32 Bit entwickel.

    Nun ist mir vor Kurzem bei dem Durchwühlen der Log-Dateien (es wird eine .log geschrieben, wenn der Try Catch Block etwas auffängt) aufgefallen das an diversen Stellen das Programm Fehler hat. Und zwar erhalte ich "System.OutOfMemoryException".

    Das Problem ereignet sich nicht/selten wenn ich die Programm-Funktionen (über 20 Arbeitsläufe) ausführe sondern Nachts, wenn ich diese in einer Zeit-aktivierten Routine nacheinander ablaufen lasse.
    In den jeweiligen einzelnen Routinen werden natürlich eine Menge an Daten in ein Array gelesen, da diese für die Verarbeitung benötigt werden. Doch an manchen Stellen, vermutlich weil sich der Speicher nicht leert, werden diese Daten nicht mehr gelesen und die jeweilige Routine stürzt wegen der oben genannten Fehlermeldung ab was fatal ist.


    Welche Möglichkeiten habe ich nun, dieses Problem zu Beheben?
    Der Speicher selber ist mehr als groß genug, das Tool läuft auf einem großen Server(64 Bit System) mit genügen Arbeitsspeicher.


    Für jede Hilfe wäre ich sehr Dankbar.
    Ggf. sind ja Programmier-Routinen von mir altbacken oder nicht Zeitgemäß.

    P.S. Die Suche habe ich natürlich angestrengt und die Windows-Routine SetProcessWorkingSetSize - zwischen den Programmen - hat auch zu keinem brauchbaren Effekt geführt.

    Bibi schrieb:

    sind ja Programmier-Routinen von mir altbacken oder nicht Zeitgemäß.

    das wäre eine möglichkeit.

    - Aber auch wird gerne vergessen z.B. bei einem StreamReader nachher zu Dispose(n) bzw. Close(n).
    - Option Strict On
    - Option Explizit On

    Es gibt unentlich viele Möglichkeiten.
    Vieleicht baust du Try Catch Ex ein und lässt dir docht genaueres in deine LOG schreiben.
    Wie du bereit schon erwänt hast.

    Dann kannst du grob einschätzen bei welcher Routine du suchen musst.

    VB.NET-Quellcode

    1. Try
    2. ' MsgBox("Versuche die Datei zu lesen ...")
    3. Datei = File.Open(Dateipfad & Dateiname, FileMode.Open) ' (1)
    4. ' MsgBox("Die Datei ist vorhanden. Es kann gelesen werden ...")
    5. Catch Ausnahme As FileNotFoundException
    6. ' MsgBox("Die Datei existiert am angegebenen Ort nicht!", MsgBoxStyle.Critical, "Fehler !")
    7. Catch Ausnahme As DirectoryNotFoundException
    8. ' MsgBox("Das angegebene Verzeichnis existiert nicht!", MsgBoxStyle.Critical, "Fehler !")
    9. Catch Ausnahme As IOException
    10. ' MsgBox(Ausnahme.Message, MsgBoxStyle.Critical, "Fehler !")
    11. Catch Ausnahme As Exception ' (2)
    12. ' MsgBox(Ausnahme.Message, MsgBoxStyle.Critical, "Fehler !")
    13. Finally ' (3)
    14. If Not Datei Is Nothing Then
    15. Datei.Close()
    16. ' MsgBox("Datei wurde ordnungsgemäß geschlossen")
    17. End If
    18. End Try

    Ich meine sowas in der Art

    mehr fällt mir leider z.Z. nicht ein
    hmm, das Problem dieser OutOfMemoryException ist, dass sie nicht immer wirklich auf ungenügenden RAM hinweist, ich hatte das ganze auch schon beim Lesen einer JPEG Datei.

    Kannst Du den Fehler nicht einengen ? Normalerweise bekommst Du doch bei einem solchen Fehler einen Stacktrace angezeigt, der Dir einen Hinweis gibt wo, wann und in welcher Methode dieser aufgetreten ist. Tritt der Fehler immer an der gleichen Stelle auf ?

    Was sagt die Überprüfung des zur Verfügung stehenden Speichers wenn die Exception auftritt ?

    ChaosBernd schrieb:

    Bibi schrieb:

    sind ja Programmier-Routinen von mir altbacken oder nicht Zeitgemäß.

    das wäre eine möglichkeit.

    Dann ist natürlich die Frage wo, was natürlich schwer ohne Quellcode ist :)


    ChaosBernd schrieb:


    - Aber auch wird gerne vergessen z.B. bei einem StreamReader nachher zu Dispose(n) bzw. Close(n).

    Den StreamReader/Write schließe ich immer mit close.
    Dispose sagt mir allerdings nichts, ist das von Wichtigkeit wenn man schon geschlossen hat.


    ChaosBernd schrieb:


    - Option Strict On
    - Option Explizit On

    Da habe ich an Beidem nichts angepackt.
    "Explizit" war eh auf On, Strict jedenfalls nicht.
    Macht es von der Performance oder meinem Problem her gesehen überhaupt Sinn?


    ChaosBernd schrieb:


    Es gibt unentlich viele Möglichkeiten.
    Vieleicht baust du Try Catch Ex ein und lässt dir docht genaueres in deine LOG schreiben.
    Wie du bereit schon erwänt hast.

    Das passiert, jede Function oder Prozedur wird geloggt.
    Daher weiß ich wohl welche Routinen das jeweilige Problem haben.
    Dennoch ist es schwierig heraus zu finden, warum dem so ist.

    Bin momentan dabei nach und nach einzelnen Routinen umzuprogrammieren.
    Beispiel:
    Ich erhalten eine große .csv Datei mit Produkte und Lagerbständen.
    Diese habe ich eingelesen in eine Array(das eine Klasse beinhaltet mit den Sturkturen).
    Dieses Array lasse ich durchlaufen.
    Ich muss nun prüfen ob dieses Produkt in der Datenbank vorhanden ist. Falls nicht, erstellen, ansonsten Updaten.
    Ich habe vorher die zu Prüfende Produkt-Tabelle in ein Array gelesen und dann das Array mit den Daten der CSV durchlaufen und mit den Daten aus der Datenbank überprüft.
    Das war wenig Performant, da ich die ganze Datenbank eingelesen hatte im Speicher(wegen dem Array).

    Nun habe ich es optimiert, in dem ich jedes mal einen Datenbank Zugriff starte. Das ist bei der Größe wesentlich schneller und verbraucht weniger Arbeitsspeicher. Hier funktioniert das Optimieren. Am Anfang eines Projektes Arbeitet man leider nicht so Sauber, wie später. Und nach über nem Jahr(fast 2) Entwicklung macht sich das dann mal Bemerkbar.

    Der nächste Punkt aber lässt sich, wie ich es gerade vermute, nicht optimieren.
    Ich möchte nun alle Lagerbestände raus werfen, die nicht im CSV File mehr sind.
    Somit muss ich alle Lagerbestands-Daten lesen und dann mit den Daten aus dem File abgleichen. Ich muss beide Daten ja im Arbeitsspeicher haben, um es ermitteln zu Können. Hier wüsste ich nicht wie man optimiert.



    Kangaroo schrieb:

    hmm, das Problem dieser OutOfMemoryException ist, dass sie nicht immer wirklich auf ungenügenden RAM hinweist, ich hatte das ganze auch schon beim Lesen einer JPEG Datei.

    Ich weiß.
    Ich habe das mal beobachtet. Beim Einlesen von einer Datenbank in ein Array - nicht überall - hat er auf einmal den Speicher in einem Augenblick so voll gehauen das er dieses Problem hatte. Die Frage war nur, warum hat er mehr in den Speicher gelesen als er kann und warum auf einmal so viel, wo diese Routine an anderen Stellen nicht so reagiert hat.
    Dabei ging es um ein reines Einlesen ein Datenbank...
    Bsp...

    VB.NET-Quellcode

    1. Public Function LeseAlleArtikel(ByVal iMandant As Integer, ByRef aProducts as clsProducts) As Integer
    2. Erase aProducts
    3. Dim myData As New DataTable
    4. Dim myAdapter As New MySqlDataAdapter
    5. Dim iItemCount As Integer
    6. Try
    7. Connection_start() 'Datenbank Verbindung starten
    8. Dim cmd As New MySqlCommand
    9. cmd.Connection = AFSconn
    10. cmd.CommandText = "SELECT * FROM Artikel WHERE (Art IS NULL OR Art = 0) AND Mandant = " + iMandant.ToString
    11. myAdapter.SelectCommand = cmd
    12. myAdapter.Fill(myData)
    13. Dim Product As clsProducts
    14. For Each dRow As DataRow In myData.Rows()
    15. Product = New clsProducts
    16. MakeProductVartausch(Product, dRow) 'An dieser Stelle werden die einzelnen Variablen der Datenbank auf die des Products angepasst. So z.b. Product.EAN = dRow....
    17. ReDim Preserve aProducts(iItemCount)
    18. aProducts(iItemCount) = Product
    19. iItemCount += 1
    20. Next
    21. Catch ex As Exception
    22. WriteLogFile(Now().ToString() + ": ERROR: " + System.Reflection.MethodBase.GetCurrentMethod().Name + vbCrLf + ex.Message)
    23. End Try
    24. Connection_Close()
    25. Return iItemCount
    26. End Function


    Das enthaltene Array wird danach direkt für weitere Verarbeitungen verwendet.


    Kangaroo schrieb:


    Kannst Du den Fehler nicht einengen ? Normalerweise bekommst Du doch bei einem solchen Fehler einen Stacktrace angezeigt, der Dir einen Hinweis gibt wo, wann und in welcher Methode dieser aufgetreten ist. Tritt der Fehler immer an der gleichen Stelle auf ?

    Ich kann die diversen Stellen - die neuerdings auftreten - feststellen.
    Manche Routinen kann ich optimieren und diese laufen beim Aufruf der jeweiligen Routine wieder.
    Aber wenn ich alle Routinen nacheinander ausführe, was ja Nachts geschieht, haben die selben Routinen doch wieder diese Problematiken.
    Und es kann ja nicht sein, das eine Routine die normal Daten aus der Datenbank in ein Array liest, so abschmiert beim Erstellen.
    Gibt es vielleicht andere Möglichkeiten, die wesentlich performanter sind, um Daten aus einer Datenbank zu Verwenden?

    Was ein Stracktrace ist weiß ich gar nicht O.o

    Kangaroo schrieb:


    Was sagt die Überprüfung des zur Verfügung stehenden Speichers wenn die Exception auftritt ?

    Ich habe den Speicher nicht überprüft, welcher noch zur Verfügung steht.
    Beim Ablauf, während ich teste, habe ich es lediglich anhand des Taskmanagers beobachtet.

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „Bibi“ ()

    Diverse Objekte benutzt man besser mit

    VB.NET-Quellcode

    1. Using sw As New StreamWriter() ' oder MySqlDataAdapter ...
    2. ' Objekt benutzen
    3. End Using

    Ggf. ist auch

    VB.NET-Quellcode

    1. Try
    2. Catch ex As Exception
    3. Finally
    4. ' hier kommt er immer vorbei, egal ob mit oder ohne Exception
    5. End Try
    nützlich
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    Ich bin zwar noch kein SQL Guru, und es wird auch auf deine Datenbank ankommen...
    Das sind jetzt nur Vermutungen die ich anstelle:

    So viel wie möglich deinen Datenbankserver arbeiten lassen.
    Die sind dafür optimiert.
    Also nicht eine ganze Abfrage in deinem Array halten, sondern
    bereits bei der Abfrage dafür sorgen, dass du so wenig wie nötig
    Daten geliefert bekommst. Damit hast du ja anscheinend schon
    angefangen.
    Das ganze CSV-File wirst du ja auch nicht einladen müssen.
    Du könntest es ja auch als Datenbank ansprechen. Oder
    temporär in eine Datenbank laden. Dann kannst du die
    verschiedenen Tabellen ja bereits bei der Abfrage abgleichen.


    Statt ein Array anzulegen, könntest du die Daten doch
    direkt wieder zur Datenbank schicken. Dort hast du ja
    bestimmt genügend Platz.

    Warum schreibst du:

    Quellcode

    1. Dim Product As clsProducts
    2. For Each dRow As DataRow In myData.Rows()
    3. Product = New clsProducts


    Setze das New doch außerhalb der Schleife.

    Lightsource schrieb:

    Warum schreibst du:

    Quellcode:
    Dim Product As clsProducts
    For Each dRow As DataRow In myData.Rows()
    Product = New clsProducts

    Setze das New doch außerhalb der Schleife.

    Product ist eine Instanz der Klasse clsProducts. Was auch immer in der Schleife mit dieser Instanz angestellt wird, es würde ohne New immer die EINE Instanz betreffen. Letztlich hätten alle Objekte der Liste die gleiche Referenz. Nur mit New wird bei jedem Durchlauf eine neue Instanz erstellt und damit kann dieses Objekt einzeln bearbeitet werden.
    ;)
    :thumbsup: Seit 26.Mai 2012 Oppa! :thumbsup:
    Ich hatte aus versehen einen Fehler in dem Quellcode oben, die gefüllte Klasse wird dann in ein Array gespeichert.

    Würde ich dies nicht tun, die Variable erneut mit Daten befüllen, so würden die Daten ohne Wert nicht überschrieben werden und so könnte ein aktuelles Produkt bei manchen Variablen den Wert des vorherigen Durchlaufs haben.

    Habe ich das jetzt so richtig verstanden, das wenn ich Product = New clsProducts angebe, es ein komplett neues Objekt erstellt wird (was sich im Speicher summiert) anstatt dieses zu leere? Weil das leeren des aktuellen Objekts war Ziel der Sache.

    Vatter schrieb:

    Mit New wird eine neue Instanz erzeugt und die alte freigegeben.

    Entschuldige wenn ich gerade etwas nicht verstehe bzw. unsicher bin.

    Wenn die alte Instanz freigegeben wird, dann ist es ja praktisch das Selbe oder?
    Würde also von der Performance/Speicherauslastung keinen Unterschied machen bzw. dürfte keine Relevanz haben, richtig?

    Ach ja: Schönen Dank schon einmal für die großartige Hilfe bisher :)

    Bibi schrieb:

    wenn ich Product = New clsProducts angebe, es ein komplett neues Objekt erstellt wird (was sich im Speicher summiert) anstatt dieses zu leere?

    Korrekt, allerdings geht die Garbage Collection (GC) in regelmässigen Abständen über den verwendeten Speicher und löscht die "nicht mehr verwendeten" Objekte. Der Algorithmus für die Ermittlung dieser Objekte ist sehr aufwändig, pauschal kann man sagen dass alle .NET Objekte gelöscht werden können die nirgendwo mehr refrenziert werden. Also auch in keinen Listen, Arrays oder sonstwas mehr geführt werden. Manchmal hilft es sicherheitshalber grosse Objekte auf Nothing zu setzen(!), noch besser Dispose zu verwenden bzw für Klassen IDisposable einzurichten.

    Auch wenn uns .NET im Gegensatz zu C heute einen Grossteil der Speicherverwaltung abnimmt: in professionellen Anwendungen mit speicherintensiven Abläufen musst Du immer selber ein Auge auf die Speicherverwaltung haben. D.h
    - sich mit Dispose / Finalize beschäftigen
    - wissen wann Du managed oder unmanaged Objekte verwendest
    - Dispose für Klassen einzurichten/aufzurufen, insbesondere unmanaged Objekte
    . auch mal die Garbage Collection selber aufzurufen

    Wie gesagt sind wir heute durch .NET sehr verwöhnt, das führt aber auch dazu dass wir solche Arbeiten oft vernachlässigen bzw. garnicht mehr so genau kennen.

    Sorry für den etwas aufgeblasenen Post ...

    Kangaroo schrieb:


    Wie gesagt sind wir heute durch .NET sehr verwöhnt, das führt aber auch dazu dass wir solche Arbeiten oft vernachlässigen bzw. garnicht mehr so genau kennen.

    Eben jenes ist das Problem,
    besagte Begriffe waren mir bis heute kein Begriff und ich war bisher nie in die Situation gekommen, mich mit solchen Dingen zu Beschäftigen.
    Was nun natürlich nachgeholt werden muss, daher dieser Thread um die Anhaltspunkte zu Erfahren.

    Dafür auf jeden Fall Danke

    Kangaroo schrieb:


    Sorry für den etwas aufgeblasenen Post ...

    Habe ich nirgends so empfunden