Heim >Backend-Entwicklung >C#.Net-Tutorial >Detaillierte Einführung in den Garbage-Collection-Mechanismus von Java und C#
(1) Grundannahmen des Garbage Collectors
(1) Objekte, denen kürzlich Speicherplatz zugewiesen wurde, müssen dies höchstwahrscheinlich tun freigelassen werden. Bevor eine Methode ausgeführt wird, ist es normalerweise erforderlich, Speicherplatz für die von der Methode verwendeten Objekte zuzuweisen. Das Durchsuchen der Sammlung kürzlich zugewiesener Objekte kann dazu beitragen, mit dem geringsten Arbeitsaufwand so viel freien Speicherplatz wie möglich freizugeben.
(2) Bei dem Objekt mit der längsten Lebensdauer ist die Wahrscheinlichkeit am geringsten, dass es freigegeben werden muss. Objekte, die nach mehreren Garbage-Collection-Runden noch vorhanden sind, sind wahrscheinlich keine temporären Objekte, die in der nächsten Garbage-Collection-Runde freigegeben werden können. Das Durchsuchen dieser Speicherblöcke erfordert oft viel Arbeit, aber nur ein kleiner Teil des Speicherplatzes freigegeben. .
(3) Objekte, denen gleichzeitig Speicher zugewiesen wird, werden normalerweise gleichzeitig verwendet. Durch die Verbindung von Objektspeicherorten, die gleichzeitig Speicher zuweisen, kann die Cache-Leistung verbessert werden.
(2) Mehrere Garbage-Collection-Mechanismen
(1) Mark-Sweep-Collector
Dieser Kollektor durchläuft zunächst den Objektgraphen und markiert erreichbare Objekte, durchsucht dann den Stapel nach nicht markierten Objekten und gibt deren Speicher frei. Diese Art von Kollektor verwendet im Allgemeinen einen einzelnen Thread zum Arbeiten und stoppt andere Vorgänge.
(2) Mark-Compression Collector
Manchmal wird er auch Mark-Sweep-Compression Collector genannt, was sich vom Mark-Sweep unterscheidet Kollektor. Gleiche Markierungsphase. In der zweiten Phase wird das markierte Objekt in einen neuen Bereich des Stapels kopiert, um den Stapel zu komprimieren. Dieser Kollektor stoppt auch andere Vorgänge.
(3) Kopierkollektor
Dieser Kollektor unterteilt den Stapel in zwei Domänen, die oft als Halbraum bezeichnet werden. Es wird jedes Mal nur die Hälfte des Speicherplatzes genutzt, und in der anderen Hälfte des Speicherplatzes werden neue, von der JVM generierte Objekte platziert. Wenn gc ausgeführt wird, kopiert es erreichbare Objekte in die andere Hälfte des Speicherplatzes und komprimiert so den Stapel. Diese Methode eignet sich für kurzlebige Objekte. Das kontinuierliche Kopieren langlebiger Objekte führt zu einer verringerten Effizienz.
(4) Inkrementeller Kollektor
Der inkrementelle Kollektor teilt den Stapel in mehrere Domänen auf und sammelt jeweils nur Müll von einer Domäne. Dies führt zu geringfügigen Anwendungsunterbrechungen.
(5) Generationssammler
Dieser Sammler unterteilt den Stapel in zwei oder mehr Domänen, um verschiedene Lebensdauerobjekte zu speichern. Neue von JVM generierte Objekte werden im Allgemeinen in einem der Felder platziert. Mit der Zeit erhalten die überlebenden Objekte eine Nutzungsdauer und werden in einen langlebigeren Bereich überführt. Generationskollektoren verwenden unterschiedliche Algorithmen für unterschiedliche Domänen, um die Leistung zu optimieren.
(6) Gleichzeitiger Kollektor
Der gleichzeitige Kollektor wird gleichzeitig mit der Anwendung ausgeführt. Diese Kollektoren müssen im Allgemeinen irgendwann andere Vorgänge (z. B. die Komprimierung) stoppen, um eine bestimmte Aufgabe abzuschließen. Da jedoch andere Anwendungen andere Hintergrundvorgänge ausführen können, wird die tatsächliche Zeit zum Unterbrechen anderer Verarbeitungen erheblich verkürzt.
(7) Parallelkollektor
Parallelkollektoren verwenden einen traditionellen Algorithmus und verwenden mehrere Threads, um ihre Arbeit parallel auszuführen. Der Einsatz von Multithreading-Technologie auf Multi-CPU-Maschinen kann die Skalierbarkeit von Java-Anwendungen erheblich verbessern.
(3) .NET Framework Garbage Collection-Mechanismus
.NET Framework enthält einen verwalteten Heap, alle Die Sprache verwendet es beim Zuweisen von Referenztypobjekten. Leichte Objekte wie Werttypen werden immer auf dem Stapel zugewiesen, aber alle Klasseninstanzen und Arrays werden in einem Speicherpool generiert, dem verwalteten Heap.
Der Garbage Collector im .NET Framework wird als Generational Garbage Collector (Generational Garbage Collector) bezeichnet, was bedeutet, dass die zugewiesenen Objekte in drei Kategorien oder für „Generation“ unterteilt sind. Sie sind 0, 1 und 2. Die anfänglichen Größen der verwalteten Heaps, die den Generationen 0, 1 und 2 entsprechen, betragen 256 KB, 2 MB bzw. 10 MB. Der Garbage Collector ändert die Größe des verwalteten Heaps, wenn er feststellt, dass eine Änderung der Größe die Leistung verbessert. Wenn eine Anwendung beispielsweise viele kleine Objekte initialisiert und diese Objekte schnell recycelt werden, erhöht der Garbage Collector den verwalteten Heap in Generation 0 auf 128 KB und erhöht die Recyclinghäufigkeit. Wenn die Situation umgekehrt ist und der Garbage Collector feststellt, dass er in Generation 0 nicht viel Speicherplatz im verwalteten Heap zurückgewinnen kann, erhöht er die Größe des verwalteten Heaps. Alle Ebenen des verwalteten Heaps sind leer, bis die Anwendung initialisiert wird. Wenn Objekte initialisiert werden, werden sie in der Reihenfolge, in der sie initialisiert wurden, in Generation 0 im verwalteten Heap abgelegt.
Objekte, denen kürzlich Speicherplatz zugewiesen wurde, werden in Generation 0 platziert. Da Generation 0 klein ist, klein genug, um in den Cache der zweiten Ebene (L2) des Prozessors zu passen, kann Generation 0 uns einen schnellen Zugriff auf Objekte ermöglichen darin. Nach einer Garbage-Collection-Runde werden Objekte, die sich noch in Generation 0 befinden, in Generation 1 verschoben. Nach einer weiteren Garbage-Collection-Runde werden Objekte, die sich noch in Generation 1 befinden, in Generation 2 verschoben. Generation 2 enthält langlebige Objekte, die mindestens zwei Sammelrunden durchlaufen haben.
Wenn ein C#-Programm Speicher für ein Objekt zuweist, kann der verwaltete Heap fast sofort den für das neue Objekt erforderlichen Speicher zurückgeben. Der Grund, warum der verwaltete Heap eine so effiziente Speicherzuweisungsleistung aufweisen kann Dies liegt daran, dass der verwaltete Heap eine relativ einfache Datenstruktur hat. Der verwaltete Heap ähnelt einem einfachen Byte-Array mit einem Zeiger auf den ersten verfügbaren Speicherplatz.
Wenn ein Block von einem Objekt angefordert wird, wird der obige Zeigerwert an die aufrufende Funktion zurückgegeben und der Zeiger wird neu angepasst, um auf den nächsten verfügbaren Speicherplatz zu zeigen. Das Zuweisen eines Blocks verwalteten Speichers ist nur geringfügig komplizierter als das Erhöhen des Werts eines Zeigers. Dies ist auch eine der Leistungsoptimierungen des verwalteten Heaps. In einer Anwendung, die nicht viel Speicherbereinigung erfordert, ist der verwaltete Heap leistungsfähiger als der herkömmliche Heap.
Aufgrund dieser linearen Speicherzuweisungsmethode werden in C#-Anwendungen gleichzeitig zugewiesene Objekte normalerweise nebeneinander auf dem verwalteten Heap zugewiesen. Diese Anordnung unterscheidet sich grundlegend von der herkömmlichen Heap-Speicherzuweisung, die auf der Speicherblockgröße basiert. Beispielsweise können sich zwei gleichzeitig zugewiesene Objekte auf dem Heap weit voneinander entfernt befinden, was die Cache-Leistung verringert. Obwohl die Speicherzuweisung schnell erfolgt, ist es daher in einigen wichtigeren Programmen wahrscheinlich, dass der verfügbare Speicher in Generation 0 vollständig belegt ist. Denken Sie daran, dass Generation 0 klein genug ist, um in den L2-Puffer zu passen, und ungenutzter Speicher nicht automatisch freigegeben wird. Wenn in Generation 0 kein gültiger Speicher zugewiesen werden kann, wird in Generation 0 eine Garbage-Collection-Runde ausgelöst. In dieser Garbage-Collection-Runde werden alle Objekte gelöscht, auf die nicht mehr verwiesen wird, und die aktuell verwendeten Objekte werden gelöscht gelöscht. Auf Generation 1 verschoben. Die Garbage Collection der Generation 0 ist die häufigste Art der Sammlung und erfolgt sehr schnell. Wenn die Garbage-Memory-Collection der 0. Generation nicht effektiv ausreichend Speicher anfordern kann, wird die Garbage-Memory-Collection der 1. Generation gestartet. Die Garbage Collection der Generation 2 sollte nur dann als letztes Mittel eingesetzt werden, wenn die Garbage Collection der Generation 1 und 0 nicht genügend Speicher bereitstellen kann. Wenn nach der Garbage Collection jeder Generation immer noch kein Speicher verfügbar ist, wird eine OutOfMemeryException ausgelöst.
(4) Java-Garbage-Collection-Mechanismus
Wie wird der Speicher beim Ausführen eines Java-Programms platziert? Im Buch „Java Programming Thoughts“ werden sechs Orte erwähnt:
(1) Register (Register)
(2) Stack ( Stack )
(3) Heap: Wird zum Platzieren aller Java-Objekte verwendet
(4) Statischer Speicher ): Wird zum Speichern vorhandener Daten verwendet. während der Programmausführung". Geändert mit statci.
(5) Konstanter Speicher
(6) Nicht-RAM-Speicherplatz: Ich verstehe darunter einen Festplattenspeicherbereich. Das heißt, Nicht-Speicherbereich.
Sun HotSpot 1.4.1 verwendet einen Generationskollektor, der den Heap in drei Hauptdomänen unterteilt: neue Domäne, alte Domäne und permanente Domäne. Alle vom JVM generierten neuen Objekte werden in der neuen Domäne platziert. Sobald ein Objekt eine bestimmte Anzahl von Garbage-Collection-Zyklen durchlaufen hat, erhält es seine Lebensdauer und gelangt in die alte Domäne. In der permanenten Domäne speichert die JVM Klassen- und Methodenobjekte. Aus Konfigurationsgründen ist die persistente Domäne eine separate Domäne und wird nicht als Teil des Heaps betrachtet. Unter diesem Gesichtspunkt sollte die JVM, die die HotSpot-Engine-Technologie verwendet, einen Garbage-Collection-Mechanismus übernehmen, der dem .NET Framework ähnelt – der Generations-Garbage-Collection-Methode.
So steuern Sie die Größe dieser Domains. Sie können -Xms und -Xmx verwenden, um die Originalgröße oder die maximale Größe des gesamten Heaps zu steuern.
Der folgende Befehl setzt die Anfangsgröße auf 128 MB:
java –Xms128m
- Xmx256m Um die Größe der neuen Domäne zu steuern, können Sie mit -XX:NewRatio den Anteil der neuen Domäne im Heap festlegen.
Der folgende Befehl setzt den gesamten Heap auf 128 m und das neue Domänenverhältnis auf 3, d. h. das Verhältnis der neuen Domäne zur alten Domäne beträgt 1:3 und die neue Die Domäne ist 1/4 des Heaps oder 32 MB:
java –Xms128m –Xmx128m
–XX:NewRatio =3 Sie können -XX:NewSize und -XX:MaxNewsize verwenden, um den Anfangswert und den Maximalwert der neuen Domäne festzulegen.
Der folgende Befehl setzt die Anfangs- und Maximalwerte der neuen Domäne auf 64m:
java –Xms256m –Xmx256m –Xmn64m
Die Standardgröße der permanenten Domain beträgt 4 m. Beim Ausführen eines Programms passt der JVM die Größe der persistenten Domäne an die Anforderungen an. Bei jeder Anpassung führt der JVM eine vollständige Speicherbereinigung auf dem Heap durch.
Verwenden Sie das Flag -XX:MaxPerSize, um die permanente Domänengröße zu erhöhen. Wenn eine WebLogic Server-Anwendung mehr Klassen lädt, ist es häufig erforderlich, die maximale Größe der permanenten Domäne zu erhöhen. Wenn die JVM eine Klasse lädt, nehmen die Objekte in der permanenten Domäne dramatisch zu, was dazu führt, dass die JVM die Größe der permanenten Domäne kontinuierlich anpasst. Um Anpassungen zu vermeiden, verwenden Sie das Flag -XX:PerSize, um einen Anfangswert festzulegen.
Als nächstes legen Sie den Anfangswert der permanenten Domäne auf 32m und den Maximalwert auf 64m fest.
java -Xms512m -Xmx512m -Xmn128m -XX:PermSize=32m -XX:MaxPermSize=64m
Standardmäßig führt HotSpot eine Replikation durch Collector wird in der Domäne verwendet. Die Domäne ist grundsätzlich in drei Teile gegliedert. Der erste Teil ist Eden, der zur Generierung neuer Objekte verwendet wird. Die anderen beiden Teile werden Rettungsräume genannt. Wenn Eden voll ist, stoppt der Kollektor die Anwendung und kopiert alle erreichbaren Objekte in den aktuellen Rettungsraum Rettungsraum. Der zu rettende Raum. Von und zu Rettungsräumen tauschen die Rollen. Objekte, die am Leben bleiben, werden im Salvage-Space repliziert, bis sie ablaufen und in die alte Domäne übertragen werden. Verwenden Sie -XX:SurvivorRatio, um die Größe des neuen Domänenunterraums zu steuern.
Wie NewRation gibt SurvivorRation das Verhältnis einer bestimmten Rettungsdomäne zum Eden-Raum an. Der folgende Befehl legt beispielsweise die neue Domäne auf 64 m fest, Eden belegt 32 m und jede Rettungsdomäne belegt 16 m:
java -Xms256m -Xmx256m -Xmn64m -XX:SurvivorRation =2
Wie bereits erwähnt, verwendet HotSpot standardmäßig den Replikationskollektor für neue Domänen und den Mark-Sweep-Komprimierungskollektor für alte Domänen. Die Verwendung eines Kopierkollektors in einer neuen Domäne ist sehr sinnvoll, da die meisten von einer Anwendung generierten Objekte nur von kurzer Dauer sind. Im Idealfall werden alle Übergangsobjekte gesammelt, wenn sie aus dem Eden-Raum verschoben werden. Wenn dies möglich ist und die aus dem Eden-Raum verschobenen Objekte langlebig sind, können sie theoretisch sofort in den alten Raum verschoben werden, um ein wiederholtes Kopieren im Rettungsraum zu vermeiden. Allerdings können Anwendungen nicht in diesen Idealzustand passen, da sie einen geringen Anteil mittel- bis langlebiger Objekte aufweisen. Es ist besser, diese mittel- bis langlebigen Objekte in der neuen Domäne zu belassen, da es kostengünstiger ist, kleine Teile der Objekte zu kopieren, als die alte Domäne zu komprimieren. Um das Kopieren von Objekten in der neuen Domäne zu steuern, können Sie mit -XX:TargetSurvivorRatio das Verhältnis des Rettungsraums steuern (dieser Wert dient zum Festlegen des Nutzungsverhältnisses des Rettungsraums. Beispielsweise ist das Rettungsraumbit 1M, und der Wert 50 bedeutet, dass 500K verfügbar sind). Der Wert ist ein Prozentsatz und der Standardwert ist 50. Wenn größere Stacks ein niedrigeres Sruvivorratio verwenden, sollte dieser Wert auf 80 bis 90 erhöht werden, um den Bailout-Raum besser zu nutzen. Verwenden Sie den Schwellenwert -XX:maxtenuring, um die Obergrenze zu steuern.
Um sicherzustellen, dass die gesamte Replikation erfolgt und Sie möchten, dass Objekte von Eden auf die alte Domäne erweitert werden, setzen Sie MaxTenuring Threshold auf 0. Nachdem die Einstellung abgeschlossen ist, wird der Rettungsraum tatsächlich nicht mehr verwendet, daher sollte SurvivorRatio auf den Maximalwert eingestellt werden, um den Eden-Raum zu maximieren. Die Einstellungen sind wie folgt:
java ... -XX:MaxTenuringThreshold= 0 –XX:SurvivorRatio=50000 …
Postscript: Wie in „Die 10 Jahre der Java Virtual Machine“ erwähnt, „ In den letzten fünf Jahren hat (JVM) die Optimierung fünf Jahre lang fortgesetzt. Eine Möglichkeit besteht darin, neue Stichprobenalgorithmen zu untersuchen, da die Stichprobenziehung mit unterschiedlichen Optimierungsstrategien zusammenhängt und einen relativ großen Einfluss darauf haben wird Gesamtleistung. Das Studium der Garbage-Collection-Algorithmen führt zu einer kurzen Pause im Programm, was zu einer negativen Benutzererfahrung führt und Verzögerungen reduzieren, wie z. B. progressive Sammlung, Zugalgorithmus usw. „Die Verbesserung der Ausführungsgeschwindigkeit und Effizienz der Sprache war schon immer das Ziel von Designern und Entwicklern, daher werden sich im Laufe der Zeit auch Garbage-Collection-Algorithmen weiterentwickeln.“ Ich glaube nicht, dass irgendein Interviewer es wagen würde, Sie zu bitten, über den Garbage-Collection-Mechanismus von C# oder Java zu sprechen (zumindest bin ich noch nicht darauf gestoßen). Sobald die Diskussion ausführlich ist, reichen viele Themen aus, um eine zu schreiben langes Buch. Aber es ist wirklich eine schöne Sache, gründlich nachzuforschen und den Ursprung zu verfolgen. Konfuzius stieg auf den Ostberg und wurde ein kleiner Lu und stieg auf den Taishan-Berg und wurde eine kleine Welt.
Das Obige ist eine detaillierte Einführung in den Garbage-Collection-Mechanismus von Java und C#. Weitere verwandte Inhalte finden Sie auf der chinesischen PHP-Website (www.php.cn).