Seltsamer Code in java.util.concurrent.linked BlockingQueue
-
27-10-2019 - |
Frage
Alle!
Ich fand seltsamen Code in LinkedBlockingQueue:
private E dequeue() {
// assert takeLock.isHeldByCurrentThread();
Node<E> h = head;
Node<E> first = h.next;
h.next = h; // help GC
head = first;
E x = first.item;
first.item = null;
return x;
}
Wer kann erklären, warum wir lokale Variable H brauchen? Wie kann es für GC helfen?
Lösung
Um besser zu verstehen, wie passiert, sehen wir sehen, wie die Liste nach der Ausführung des Codes aussieht. Betrachten Sie zunächst eine erste Liste:
1 -> 2 -> 3
Dann h
verweist auf head
und first
zu h.next
:
1 -> 2 -> 3
| |
h first
Dann h.next
verweist auf h
und head
verweist auf first
:
1 -> 2 -> 3
| / \
h head first
Jetzt wissen wir praktisch, dass nur aktive Referenz auf das erste Element zeigt, das für sich selbst ist (h.next = h
), und wir wissen auch, dass das GC Objekte sammelt, die keine aktiven Referenzen haben. Wenn die Methode endet h
existiert nur im Rahmen der Methode.
Trotzdem wurde darauf hingewiesen, und ich stimme dem zu, dass selbst mit der klassischen Dequeue -Methode (dh nur machen first
zeigen auf head.next
und head
zeigen auf first
) Es gibt keine Referenzen mehr auf den alten Kopf. In diesem Szenario bleibt der alte Kopf jedoch in Erinnerung und hat immer noch seine next
Feld zeigt auf first
, Während in dem Code, den Sie gepostet haben, ist das einzige, was übrig ist, ein isoliertes Objekt, das auf sich selbst zeigt. Dies kann den GC dazu bringen, schneller zu handeln.
Andere Tipps
Wenn Sie sich das JSR166 SRC ansehen, finden Sie das beleidigende Commit
http://gee.cs.oswego.edu/cgi-ner/viewcvs.cgi/jsr166/src/main/java/util/concurrent/linkedBlockingQueue.java?view=log(siehe v 1.51)
Dies zeigt, dass die Antwort in diesem Fehlerbericht liegt
http://bugs.sun.com/view_bug.do?bug_id=6805775
Die vollständige Diskussion ist in diesem Thread
http://thread.gmane.org/gmane.comp.java.jsr.166-concurrency/5758
Bei dem "Helping GC" -Bit geht es darum, Dinge zu vermeiden, die in die Begrenzung bluten.
Prost
Matt
Vielleicht etwas spät, aber die aktuelle Erklärung ist für mich völlig unbefriedigend und ich glaube, ich habe eine vernünftigere Erklärung.
Zuallererst macht jeder Java GC eine Art von einer Art von einem Wurzelsatz auf die eine oder andere Weise. Dies bedeutet, dass wir das nicht lesen werden, wenn der alte Kopf gesammelt wird next
Variable sowieso - es gibt keinen Grund dazu. Somit WENN Der Kopf wird in der nächsten Iteration gesammelt, dass es keine Rolle spielt.
Das IF im obigen Satz ist der wichtige Teil hier. Der Unterschied zwischen der Einstellung neben etwas anderes spielt keine Rolle für das Sammeln von Kopf selbst, kann jedoch einen Unterschied für andere Objekte bewirken.
Nehmen wir an, ein einfacher Generations -GC: Wenn der Kopf im jungen Satz ist, wird er im nächsten GC sowieso gesammelt. Aber wenn es im alten Satz ist, wird es nur gesammelt, wenn wir einen vollen GC machen, der selten vorkommt.
Was passiert also, wenn der Kopf im alten Set ist und wir einen jungen GC machen? In diesem Fall geht die JVM davon aus, dass jedes Objekt im alten Haufen noch lebt und jede Referenz von alten zu jungen Objekten zum Wurzelsatz für den jungen GC fügt. Und genau das vermeidet die Aufgabe hier: Das Schreiben in den alten Haufen ist im Allgemeinen mit einer Schreibbarriere oder so etwas geschützt, damit die JVM solche Aufgaben erfassen und sie richtig handhaben kann - in unserem Fall wird das Objekt entfernt next
aus dem Stammsatz ausgewiesen, der Konsequenzen hat.
Kurze Beispiel:
Angenommen, wir haben 1 (old) -> 2 (young) -> 3 (xx)
. Wenn wir jetzt 1 und 2 aus unserer Liste entfernen, können wir erwarten, dass beide Elemente vom nächsten GC gesammelt werden. Aber wenn nur ein junger GC auftritt und wir das nicht entfernt haben next
Zeiger in alt, beide Elemente 1 und 2 werden nicht gesammelt. Entgegen diesem, wenn wir den Zeiger in 1 entfernt haben, wird 2 vom jungen GC gesammelt.
Hier ist ein Code -Beispiel, das die Frage veranschaulicht: http://pastebin.com/ztslpupq. Ein GC danach durchführen runWith()
Und ein Heap -Dump für beide Versionen zu nehmen, sagt, dass es nur eine Elementinstanz gibt.