Wenn ich die Verwendung virtueller Bytes in meinem Programm beobachte, während es läuft, zeigte sich, dass die Anzahl der virtuellen Bytes in einigen Schritten um etwa 1 GB in etwa 5 Minuten steigt. Das Programm befasst sich mit TCP-Sockets und hohen Datenübertragungsdurchsatz zwischen ihnen (~ 800Mbps).
Das Laden einer Dump-Datei des Programms in windbg hat gezeigt, dass der Grund für die sehr hohe und schnelle Speicherbelegung ungefähr 1GB "freie" Objekte ist. Wenn ich den Garbage Collector (gen 0, 1 & amp; 2) vom Konsolenbildschirm des Programms aus anrufe (nachdem ich ihn in diesen Zustand gebracht habe), wird ungefähr 1 GB Speicher belegt.
Ich versuche zu verstehen, was genau diese freien Objekte sind und warum sie nicht automatisch vom Müllsammler gesammelt werden.
Bearbeiten: Ein Vorschlag war, dass ich die Objekte im Large Object Heap erstellen könnte und es wird fragmentiert, aber das ist nicht der Fall, da ich alle "freien" Objekte gesehen habe sitzt in Gen 2 Heap.
Ein anderer Vorschlag war, dass möglicherweise Gen 2 Heap wegen gepinnter Objekte fragmentiert wird, aber wenn das der Fall wäre, würde GC.Collect das Problem nicht beheben, aber es ist tatsächlich so, glaube ich, dass dies auch nicht der Fall ist.
Was ich aus der Diskussion mit Paul vermute, ist, dass der Speicher freigegeben wird, aber aus irgendeinem Grund selten oder nur dann in das Betriebssystem zurückkehrt, wenn ich GC.Collect manuell aufruft.
Sie sind keine freien 'Objekte', sie sind freier Raum. .NET gibt den von ihm verwendeten Speicher nicht sofort an das Betriebssystem zurück. Alle freien Blöcke können für nachfolgende Objektzuordnungen verwendet werden, vorausgesetzt, sie passen in den freien Block (andernfalls muss der Heap erweitert werden, indem das Betriebssystem aufgefordert wird, mehr Speicher zuzuweisen).
Der Garbage Collector unternimmt Anstrengungen, um freien Speicherplatz durch Komprimieren von Generation 2 zu großen nutzbaren Blöcken zu kombinieren. Dies ist nicht immer möglich: Beispielsweise kann eine Anwendung Objekte anheften, die möglicherweise verhindern, dass der Garbage Collector freien Speicherplatz durch Verschieben der Live-Objekte kombiniert an die Vorderseite des Heaps. Wenn dies viel passiert, wird der Speicher der Anwendung in nutzlose kleine Blöcke aufgeteilt und dieser Effekt wird als "Heap-Fragmentierung" bezeichnet.
Außerdem gibt es einen Large Object Heap (LOH), in dem größere Objekte zugewiesen werden. Die Begründung ist, dass die Heap-Komprimierung mit Kosten verbunden ist, da Daten kopiert werden müssen und daher die LOH nicht komprimiert wird, wodurch diese Kosten vermieden werden. Die Kehrseite ist jedoch, dass die LOH leicht fragmentiert werden kann, mit kleinen, weniger nützlichen Blöcken von freiem Speicher zwischen Live-Objekten.
Ich würde vorschlagen, ein dumpheap -stat
auszuführen. Dieser Befehl meldet alle fragmentierten Blöcke am Ende der Liste. Sie können diese Blöcke dann ablegen, um eine Vorstellung davon zu bekommen, was passiert.
Übrigens sieht es so aus, als ob Sie ein bekanntes Problem (zumindest unter Socket-Gurus) haben, dass die meisten Socket-Server in .Net einsteigen. Paul hat bereits erwähnt, was es bedeutet. Um mehr zu erläutern, was schief läuft, ist, dass während eines Lese- / Schreibvorgangs auf dem Socket der Puffer angeheftet wird - was bedeutet, dass der GC es nicht bewegen darf (als solches sind Ihre Heap-Fragmente). Coversant (der die Lösung entwickelte) sah eine OutOfMemoryException, wenn ihre tatsächliche Speicherbelegung nur etwa 500 MB betrug (aufgrund einer solchen starken Fragmentierung). Es zu beheben ist eine andere Geschichte.
Was Sie tun wollen, ist beim Start der Anwendung eine sehr große Menge an Puffern zuweisen (ich mache derzeit 50MB). Sie werden die neuen Klassen ArraySegment<T>
(v2.0) und ConcurrentQueue<T>
(v4.0) besonders nützlich finden, wenn Sie dies schreiben. Es gibt ein paar blockierungsfreie Warteschlangen auf den Röhren, wenn Sie noch nicht v4.0 verwenden.
Danach müssen Sie NetworkStream
ableiten und den eingehenden Puffer mit einem aus Ihrem Pufferpool auslagern. Buffer::BlockCopy
unterstützt die Leistung ( nicht Array::Copy
verwenden). Das ist kompliziert und haarig; vor allem, wenn Sie es async-fähig machen.
Wenn Sie Streams nicht übereinander legen (zB SSLStream
& lt; - & gt; DeflateStream
& lt; - & gt; XmlWriter
usw.), sollten Sie das neues Socket-Async-Muster in .Net 4.0 ; Das hat mehr Effizienz um die IAsyncResult
s, die herumgereicht werden. Da Sie keine Streams überlagern, haben Sie die volle Kontrolle über die verwendeten Puffer - Sie müssen also nicht auf die NetworkStream
Unterklassenroute gehen.
Tags und Links .net c# garbage-collection memory-management windbg