Zum Inhalt springen

Speicherleck im .Net?


NancyG

Empfohlene Beiträge

Hallo Leute,

ich habe eine Anwendung die immer laufen soll! Nun ist es aber so, dass dort der Speicher *gefressen* wird.

Also habe ich ich mal eine kleine Testanwendung geschrieben - und das Szenario vereinfacht nachgestellt.

Es wird auf Kommando ein Thread gestartet - und auf Kommando auch wieder gestoppt (gelöscht). Leider wird bei diesem Vorgang der Speicher nicht wieder vollends freigegeben.

Konsolenanwendung:


Dim thTest As Threading.Thread

Dim lineRead As String

Sub Main()

    Do

        lineRead = Console.ReadLine()

        If Not commandParse(lineRead) Then

            Console.WriteLine("command not found: " & lineRead)

        End If

        If lineRead = "exit" Then Exit Do

        GC.Collect()

        GC.WaitForPendingFinalizers()

        GC.Collect()

        GC.WaitForPendingFinalizers()

    Loop

End Sub


Function commandParse(ByVal input As String) As Boolean

    Select Case input

        Case "exit"

            dis()

        Case "dmexit"

            dis()

        Case "dmcon"

            con()

        Case Else

            Return False

    End Select

    Return True

End Function


Sub myThread()

    While thTest.IsAlive

        Threading.Thread.Sleep(10)

    End While

End Sub


Sub con()

    If thTest Is Nothing Then

        thTest = New Threading.Thread(AddressOf myThread)

        thTest.Start()

    End If

End Sub


Sub dis()

    If Not thTest Is Nothing Then

        thTest.Abort()

        thTest = Nothing

    End If

End Sub


Ich habe mir das Tool .Net Memory Profiler installiert und mal geschaut, wo es denn klemmt.

Was ich festgestellt habe, dass im .Net der Speicher wieder frei wird, nur der vMem und der heapMem vom System nicht mehr.

Vorm Starten:

post-83586-14430449588202_thumb.png

Beim laufenden Thread:

post-83586-1443044958853_thumb.png

Nach beenden des Threads:

post-83586-14430449588809_thumb.png

Wo liegt das Problem - und wie kann ich es lösen?

vG Nancy

Link zu diesem Kommentar
Auf anderen Seiten teilen

Windows gibt Speicher nicht "direkt frei" wenn du ein Programm beendest sondern dann wenn er benötigt wird. Das heißt nur weil z.B. im Taskmanager nach dem Beenden deines Programms noch mehr Arbeitsspeicher als in Verwendung angezeigt wird auls vorher heißt nicht das irgendwo ein Speicherleck ist. Das wäre erst der Fall wenn der Speiher wirklich benötigt werden würde aber nicht zur Verfügung stände.

Genauso solltest du den GC nicht selber aufrufen, da gibt es eigentlich nur sehr selten Anlass zu. Der arbeitet nämlich auch so das er unnötige Arbeitslast vermeidet und seinen Speicher in bestimmten Abständen oder wenn wirklich Speicher benötigt wird aufräumt.

Link zu diesem Kommentar
Auf anderen Seiten teilen

Rule #1

Don't.

This is really the most important rule. It's fair to say that most usages of GC.Collect() are a bad idea and I went into that in some detail in the orginal posting so I won't repeat all that here. So let's move on to...

When to call GC.Collect() - Rico Mariani's Performance Tidbits - Site Home - MSDN Blogs

Du solltest bedenken, dass Du in der Regel mit C#/VB.NET sog. managed Code schreibst. Das heißt, dieser Code verhält sich anders als C/C++ - Code. Während Du unter C++ einen Destruktor hast, Du unter C/C++ Speicherblöcke nach belieben reservieren und wieder freigeben kannst, geht das bei managed Code nicht.

Der Garbage Collector kümmert sich darum, wann welche Objekte wieder frei gegeben werden.

Frei nach "Onkel Bob": »So you are writing Code in Java [man könnte auch .NET sagen]? Why care about memory anyways?«

Link zu diesem Kommentar
Auf anderen Seiten teilen

Falsch!

Das Problem liegt an deinem:


thTest.Abort()

Lass mal deinen Thread normal beenden. Dann wird auch der Speicher frei.

//globale variable zum beenden des Thread

Dim thStopThread as Bool


Sub dis()

    If Not thTest Is Nothing Then

        thStopThread = true

    End If

End Sub


Sub myThread()

    While thTest.IsAlive and not thStopThread

        Threading.Thread.Sleep(10)

    End While

    thTest = nothing

    thStopThread = false

End Sub

Ungetestet - sollte aber funktionieren.

CreateThread: Remarks lesen

@Guybrush Threepwood:

Zeig mir mal bitte wo das steht!

Windows gibt Speicher nicht frei! Sondern nur, wenn er benötigt wird.

1. Wird sehr wohl der Speicher freigegeben, wenn der letzte Thraed eines Prozesses beendet wird.

2. Wird der Speicher auch freigegeben, wenn man ihn über (malloc reserviert) free wieder freigibt.

zu Net:

Wie dort steht, war das ein Testfall, um den Speicherverbrauch zu messen/simulieren.

Also spricht nichts gegen ein GC.Collect

OT:

Du solltest bedenken, dass Du in der Regel mit C#/VB.NET sog. managed Code schreibst. Das heißt, dieser Code verhält sich anders als C/C++ - Code. ...

Ähm... hast du gelesen was sie geschrieben hat. Und hast du dir auch mal die Bilder angesehen?

Link zu diesem Kommentar
Auf anderen Seiten teilen

Also Thread.Abort lässt den Thread auch "normal" beenden, er wird halt abgebrochen. Und zwar genau an der Stelle, wo er sich gerade befindet, dabei wird eine Excption im Thread ausgelöst. Und wenn man die "ThreadAbortException" nicht richtig behandelt, können dann keine Ressourcen mehr freigegeben werden, die man im Thread angelegt hat. Der Thread selber ist dann trotzdem beendet. Sauberer ist es aber auf jeden Fall mit einem Flag.

(Gerade weil es ein so ein einfacher Testfall ist, hilft GC.Collect hier überhaupt nicht weiter. Im Codebeispiel spielt der aber sowieso keine Rolle, da der Screenshot nach dem Beenden des Threads gemacht wurde, also in der "DO-LOOP". Zum Speicherverbrauch messen eignen sich Profiler und oder Performance Counter. )

Aber jetzt auch mal was zur eigentlichen Frage ;)

@NancyG: Meinst Du wirklich die Werte aus den Zeilen "Kernel"? Das sind Systemwerte, auf die alle Anwendungen Einfluss haben (nicht nur Dein .NET-Code).

Link zu diesem Kommentar
Auf anderen Seiten teilen

Hallo,

danke für die Antworten.

Ich habe jetzt weiter getestet.

Aber es funktioniert auch nicht, wenn ich den Thread normal beenden lasse (über Flag).

Die geöffneten Handles bleiben offen. Demzufolge wird auch der Speicher nicht freigegeben.

Ich habe das ganze nochmal unter c++ getestet, dort wird der Thread beendet - und der Speicher ist wieder FREI.


CloseHandle( CreateThread(....)); //handle gleich schliessen

Aber wie kann man im .Net die Handles wieder schließen?

Bei der Testanwendung sind nach dem Starten 104 Handles offen. Wird der Thread gestartet - sind es 109.

Nach dem Beenden des Threads gehen die Handles nach Ewigkeiten wieder runter - auf 108! Was aber nicht die Ausgangssituation war. Es müssten doch wieder 104 werden.

Wie oben schon geschrieben. Das ganze im C++ - macht genau - was man erwartet.

Was muss ich an dem Code noch verändern, um das gewünschte Ergebnis zu erzielen - den Speicher wieder frei zu bekommen.

vG Nancy

Link zu diesem Kommentar
Auf anderen Seiten teilen

@Guybrush Threepwood:

Zeig mir mal bitte wo das steht!

Windows gibt Speicher nicht frei! Sondern nur, wenn er benötigt wird.

1. Wird sehr wohl der Speicher freigegeben, wenn der letzte Thraed eines Prozesses beendet wird.

2. Wird der Speicher auch freigegeben, wenn man ihn über (malloc reserviert) free wieder freigibt.

Deswegen die Anführungszeichen. Viele Leute gehen davon aus das wenn sie ein Programm beenden und dann zum Beispiel im Taskmanager sehen das der Speicherverbrauch trotzdem nicht wieder auf den Stand vor dem Programmstart zurückgegangen ist das ein Speicherleck in dem Programm vorliegt, was aber nicht der Fall sein muss.

Was muss ich an dem Code noch verändern, um das gewünschte Ergebnis zu erzielen - den Speicher wieder frei zu bekommen.

Gar nichts. Dein Code macht ja auch nichts außer einen Thread zu starten und wieder zu beenden. Bis auf die unnötigen bzw. falschen GC Aufrufe ist da alles in Ordnung dran. Um alles was die Freigabe der Resourcen betrifft kümmert sich der GC von alleine, da musst du normalerweise nur selber etwas machen wenn du irgendwelche unmanged Resourcen verwendest.

Link zu diesem Kommentar
Auf anderen Seiten teilen

Ok, mal abgesehen davon, dass GC aufgerufen wird - soll an dem Code alles richtig sein!

Das nehme ich erst einmal so hin.

Aber!: Ich habe nach jedem Aufruf, der einen Thread erzeugt, Handles - die nicht geschlossen werden und auch Speicher, der nicht mehr freigegeben wird. Und das ist nicht richtig!

@Guybrush Threepwood

Auch wenn du mir jetzt wieder mit: Der GC kümmert sich darum, kommst. Es ist nicht normal - und er kümmert sich auch nicht darum.

Fakt:

In meiner Anwendung wird aller 250ms ein Thread erzeugt. Mittlerweile habe ich eine EXE im Taskmgr, die >1GB RAM belegt (virt: >1GB, phys: < 300MB) und der GC tut gar nichts.

Ich werde das Programm so lange laufen lassen, bis der Speicher alle ist :D - und ich vermute, dass der GC nichts wegräumt - und es zu einer OutOfMemoryException kommt.

vG Nancy

Link zu diesem Kommentar
Auf anderen Seiten teilen

@Klotzkopp

Es ist kein Designfehler:

Schon mal von WinCC/ODK gehört? Es werden aller 250ms Tags (Variablen) in meiner Anwendung aktualisiert.

@lbm1305

Weil es nichts bringt?

vG Nancy

ps. Probiert es doch selber aus.

C: Thread erstellen -> beenden -> Handles schliessen -> Speicher ist wieder frei!

.Net:Thread erstellen -> beenden -> Handles bleiben offen -> Speicher ist nicht wieder frei! -Und der GC räumt den Dreck auch nicht weg.

@lbm1305

Dein "Gefällt mir": Wofür?

Bearbeitet von NancyG
Link zu diesem Kommentar
Auf anderen Seiten teilen

Eben nicht,

weil die weitere Verarbeitung der Variablen eben nicht nur so aussieht:

Variablen werden in der Anwendung aktualisiert: Sondern - aller 250ms die aktuellen Variablen zBsp.:

- über TCP/IP wo anders hin sollen

- in ein Textfile geschrieben werden

- in eine Datenbank geschrieben werden

- usw.

Und für jede Aktion: TCP/IP, Textfile, DB wird ein eigener Thread erzeugt.

Aber das steht auch nicht zur Debatte:

Fakt - nach jedem Thread der erstellt wurde, wird der Speicher nicht freigegeben. Es geht hier also nicht um die Frage:

Ob das Design der AW richtig/falsch ist!

Die Frage lautet, warum wird der Speicher nicht freigegeben.

Und es hat auch nichts mit Echtzeit zu tun (Definition Echtzeit lesen.)!

Bitte Leute!: Nehmt doch Bezug auf die Fragestellung - und sagt mir nicht, dass ich es mit C++ machen soll (was ich persönlich sowieso möchte). Oder: Dass sich um den Speicher der GC kümmert, denn dies tut er nicht.

Ich habe es auch ohne die GC-Aufrufe probiert. Es bleiben Handles offen - und demzufolge auch Speicher hängen - und das bei jedem Threadaufruf!

vG Nancy

Link zu diesem Kommentar
Auf anderen Seiten teilen

Und für jede Aktion: TCP/IP, Textfile, DB wird ein eigener Thread erzeugt.
Also erzeugst du alle 250 ms drei neue Threads? Das ist - ich sag's nochmal - totaler Blödsinn. Das Erzeugen eines Threads ist mit einem nicht zu vernachlässigenden Overhead verbunden. So etwas macht man nicht, damit zwingst du jedes System in die Knie. Genau dafür gibt es Threadpools. Erzeuge Job-Objekte, und lasse sie von einem Threadpool abarbeiten. Dein jetztiges System verbrät einen Großteil seiner Zeit unnötigerweise mit dem Erzeugen neuer Threads.

Die Frage lautet, warum wird der Speicher nicht freigegeben.
Du kommst mir vor wie jemand, der auf eine Antwort besteht, warum seine Hände bluten, wenn er sich mit der Kettensäge die Fingernägel reinigt, und sich aufregt, wenn ihm jemand rät, eine Nagelschere zu benutzten.

Ich gehe davon aus, dass dein Speicherproblem verschwindet, sobald du Threads nicht mehr mißbrauchst.

Link zu diesem Kommentar
Auf anderen Seiten teilen

  • 2 Wochen später...

Ich weiß auch nicht, ob ein Threadpool die richtige Lösung ist. Er will alle 250ms ein Thread erstellen, egal ob x Threads bereits laufen. Bei einem Threadpool werden die Threads aber ab x Stück in eine Warteschleife eingereiht und erst später gestartet. Also dann nicht nach 250ms gestartet sondern "irgendwann". Auf der anderen Seite, kann man nur bis an die Grenzen viele normale Threads starten. Und die Grenze ist ja erreicht.

Link zu diesem Kommentar
Auf anderen Seiten teilen

Er will alle 250ms ein Thread erstellen, egal ob x Threads bereits laufen. Bei einem Threadpool werden die Threads aber ab x Stück in eine Warteschleife eingereiht und erst später gestartet. Also dann nicht nach 250ms gestartet sondern "irgendwann".
Wenn die Systemleistung nicht ausreicht, um die anfallende Arbeit zeitnah zu erledigen, gibt es einen "Rückstau". Das ist aber immer noch besser, als das System durch immer neue Threads immer mehr zu belasten. Das dürfte das Problem eher noch verschärfen.

Letzendlich muss dieselbe Arbeit erledigt werden, egal wie man das löst. Aber ein Threadpool erzeugt deutlich weniger Overhead, so dass mehr Leistung für die eigentliche Arbeit bleibt.

Link zu diesem Kommentar
Auf anderen Seiten teilen

Da hast du vollkommen Recht, Klotzkopp. Ich würde aber erstmal schauen, was überhaupt gemacht wird:

aller 250ms die aktuellen Variablen zBsp.:

- über TCP/IP wo anders hin sollen

- in ein Textfile geschrieben werden

- in eine Datenbank geschrieben werden

- usw.

Und für jede Aktion: TCP/IP, Textfile, DB wird ein eigener Thread erzeugt.

Das ist die Anforderung. Wie wäre es dann, wenn man die Daten erst einmal alle 250ms sammelt (in einem Thread mit geringer Auslastung, der innerhalbt von 250ms laufen und bestenfalls beendet werden kann) und dann bei geringer Auslastung entsprechend (auf einmal) schreibt? Ich denke, der Overhead entsteht durch die ganzen Verbindungen, zB sind Datenbankverbindungen und erst recht die Abfragen ziemlich Ressourcenfressend. Dass dies (auf Dauer) nicht unter < 250ms geschieht, ist klar, zumal die Datenbank sich meist auf einem anderen Server befindet als die Applikation.

Link zu diesem Kommentar
Auf anderen Seiten teilen

Das ist die Anforderung.
Nein, das ist bereits eine Lösung, eine Umsetzung einer Anforderungen.

Ich denke, der Overhead entsteht durch die ganzen Verbindungen, zB sind Datenbankverbindungen und erst recht die Abfragen ziemlich Ressourcenfressend. Dass dies (auf Dauer) nicht unter < 250ms geschieht, ist klar, zumal die Datenbank sich meist auf einem anderen Server befindet als die Applikation.
Das ist klar? Du scheinst mir eine Kristallkugel in der Tasche zu haben ;)
Link zu diesem Kommentar
Auf anderen Seiten teilen

Wenn die Systemleistung nicht ausreicht, um die anfallende Arbeit zeitnah zu erledigen, gibt es einen "Rückstau". Das ist aber immer noch besser, als das System durch immer neue Threads immer mehr zu belasten. Das dürfte das Problem eher noch verschärfen..

Das Problem ist doch nicht die Leistung, sondern die Ressource (Speicher). Die 3 Threads werden schon in den 250ms abgearbeitet. Es ist sehr selten, dass mal eine Queue von 1 (aber nie höher) entsteht (Mutex WaitOne/ReleaseMutex).

Ich habe es mit ThreadPoll versucht - und es bleibt das selbe Phänomen. Es starten x Threads, Handels werden erstellt, Speicher wird alloziert. Nach beenden der Threads bleiben die Handles einfach offen - und damit auch der Speicher belegt.


Private Sub th()

    Dim i As Int32 = 0

    While i < 100

        i += 1

        Threading.Thread.Sleep(100)

    End While

End Sub


Private Sub StartTH_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles StartTH.Click

    For i As Int32 = 0 To 20 Step 1

        Threading.ThreadPool.QueueUserWorkItem(New Threading.WaitCallback(AddressOf th))

        Threading.Thread.Sleep(50)

    Next

End Sub

Wer hier so toent wird doch in der MSDN Threadpools nachschlagen koennen, oder?

Kann ich - und habe ich auch gemacht. Nur leider bringt mich das eben nicht weiter. (s.o.)

Bearbeitet von NancyG
Link zu diesem Kommentar
Auf anderen Seiten teilen

Dein Kommentar

Du kannst jetzt schreiben und Dich später registrieren. Wenn Du ein Konto hast, melde Dich jetzt an, um unter Deinem Benutzernamen zu schreiben.

Gast
Auf dieses Thema antworten...

×   Du hast formatierten Text eingefügt.   Formatierung wiederherstellen

  Nur 75 Emojis sind erlaubt.

×   Dein Link wurde automatisch eingebettet.   Einbetten rückgängig machen und als Link darstellen

×   Dein vorheriger Inhalt wurde wiederhergestellt.   Editor leeren

×   Du kannst Bilder nicht direkt einfügen. Lade Bilder hoch oder lade sie von einer URL.

Fachinformatiker.de, 2024 by SE Internet Services

fidelogo_small.png

Schicke uns eine Nachricht!

Fachinformatiker.de ist die größte IT-Community
rund um Ausbildung, Job, Weiterbildung für IT-Fachkräfte.

Fachinformatiker.de App

Download on the App Store
Get it on Google Play

Kontakt

Hier werben?
Oder sende eine E-Mail an

Social media u. feeds

Jobboard für Fachinformatiker und IT-Fachkräfte

×
×
  • Neu erstellen...