Nehmen wir an, es handelt sich um einen gleichzeitigen Mark-and-Sweep-Garbage-Collector.
Wenn ein solcher GC konstante Zeiger verarbeitet, geht er nur durch diese (ausgehend von den Wurzeln) und markiert jeden angetroffenen Datenblock. Dann fegt alles unmarkiert. Ein Client-Code sollte die Datenblöcke markieren, die er als Roots verwendet.
Aber was tun mit Variablen? Hier ist eine Situation:
V
ist eine Variable, die einen Zeiger auf ein Objekt speichertA
.Thread 1
liestV
und suspendiert.Thread 2
ändertV
und zeigt auf das ObjektB
.- Der Garbage Collector führt seine "Mark" -Phase aus und trifft auf Begegnungen, auf die
A
nicht mehr verwiesen wird. Anschließend wird die Zuordnung während der "Sweep" -Phase aufgehoben. Thread 1
erwacht und versucht zu verwendenA
(bereits ausV
Schritt 2 gelesen ), indem es als root markiert wird. Und scheitert , weilA
es nicht mehr existiert.
Wie geht man damit um?
Das Thread 2
kann das ersetzte Objekt A
mit einem speziellen Nicht-Entfernen-Flag markieren (ein ähnliches Flag wird für neu zugewiesene Objekte verwendet). Aber wann sollte diese Flagge entfernt werden? Das Thread 1
könnte ich natürlich tun. Aber Thread 2
nichts weiß Thread 1
, und somit nicht sicher sein kann , dass dies jemals geschehen wird. Dies kann dazu führen, A
dass niemals befreit wird. Und wenn GC dieses Flag entfernt, verhindert nichts, dass A
es entfernt wird, wenn GC zum zweiten Mal ausgeführt wird ...
Die spontanen Mark-and-Sweep-Garbage-Collector-Beschreibungen, die ich gelesen habe, erwähnen nur, dass das ersetzte Objekt "grau" sein sollte. Aber ohne Einzelheiten. Ein Link zu einer detaillierteren Beschreibung der Lösung wäre sehr willkommen.
quelle
Sie müssen lokale Variablen irgendwann während der Markierungsphase markieren. Alle lokalen Variablen, einschließlich derjenigen, die normalerweise auf einem Stapel leben. Irgendwie.
Ich denke außerdem, dass dies während der synchronen Phase (alle Mutatoren gestoppt) des Scannens modifizierter Objekte erfolgen muss. Tatsächlich kann das gleiche Problem auch ohne Berücksichtigung lokaler Variablen / Register auftreten. Betrachten Sie Objekt A, das auf Null zeigt, und Objekt B, das auf C zeigt. Jetzt scannen Sie Objekt A, ein Mutator-Thread kommt, kopiert den Verweis auf C von B nach A, macht B null. Und jetzt kommen Sie zum Scannen von B. Und C rutschte aus unter deinen Fingern.
Ich kenne keine Möglichkeit, damit umzugehen, bei der die Mutatoren nicht gestoppt werden müssten. Die übliche Technik besteht darin, am Ende der Markierungsphase alle Mutatoren zu stoppen und alle Objekte, die sie während der Hauptmarkierungsphase mutiert haben, erneut zu markieren. Und fügen Sie Stapel und Register hinzu.
Das Markieren von Registern wird normalerweise umgangen, indem es synchron ausgeführt wird, indem manchmal der Kollektor im Thread aufgerufen wird. Innerhalb der Collector-Funktion können sich nur eigene lokale Variablen (die keine Roots sind) in Registern befinden. Alle anderen lokalen Variablen in der Aufrufkette befinden sich im Stapel, sodass Sie den Stapel durchlaufen können.
Alternativ können Sie ein Signal an den Thread senden. Der Signalhandler erzwingt erneut alle Variablen auf dem Stapel, sodass Sie sie durchlaufen können. Nachteil dieser Methode ist, dass sie plattformabhängig ist.
quelle