2012-01-11 9 views
11

Todo!Código extraño en java.util.concurrent.LinkedBlockingQueue

me pareció extraño código en 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; 
} 

que puede explicar por qué necesitamos variable local h? ¿Cómo puede ayudar a GC?

+0

tal vez no está actualizado el comentario (se puede encontrar el control de versiones y localizar el comentario para la confirmación?) :) – milan

+2

Es interesante que esta var temp y comentario se añadieron sólo en java 7. Entonces definitivamente fue por alguna intención. Consulta http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b14/java/util/concurrent/LinkedBlockingQueue.java#LinkedBlockingQueue.extract%28%29 y http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/7-b147/java/util/concurrent/LinkedBlockingQueue.java#LinkedBlockingQueue.dequeue%28%29 – Vadzim

+0

que es openjdk, LinkedBlockingQueue en el Oracle jdk 6 en Mac (1.6.0_29-b11-402.jdk) tiene la variable de temperatura. – milan

Respuesta

1

Para comprender mejor qué sucede, veamos cómo se ve la lista después de ejecutar el código. Consideremos en primer lugar una lista inicial:

1 -> 2 -> 3 

Entonces h puntos a head y first a h.next:

1 -> 2 -> 3 
| | 
h first 

Entonces h.next puntos a h y head puntos a first:

1 -> 2 -> 3 
| /\ 
h head first 

Ahora, practicamente sabemos que th Sólo hay referencia activa apuntando al primer elemento, que es por sí mismo (h.next = h), y también sabemos que el GC recopila objetos que no tienen más referencias activas, por lo que cuando el método finaliza, el (antiguo) encabezado de la lista ca debe ser recolectado de manera segura por el GC ya que h existe solo dentro del alcance del método.

Una vez dicho esto, se señaló, y estoy de acuerdo con esto, que incluso con el método clásico de quitar de la cola (es decir, sólo hacer first punto a head.next y head punto a first) no hay más referencias que apuntan hacia la cabeza de edad . Sin embargo, en este escenario, la cabeza anterior se deja colgando en la memoria y todavía tiene su campo next apuntando hacia first, mientras que en el código que publicó, lo único que queda es un objeto aislado que apunta a sí mismo. Esto puede provocar que el GC actúe más rápido.

+0

Causa de bajada h ya no tendría más referencias incluso sin 'h.next = h'. – Vadzim

+1

@Vadzim: No estaba diciendo eso, solo estaba explicando lo que sucede en el código que publicó. – Tudor

+0

La pregunta no es sobre eso. Se trata de por qué no solo 'first = head = head.next'. Pero gracias por los gráficos de todos modos. Tenga en cuenta que la cabecera siempre no contiene el primer elemento en sí. – Vadzim

0

Aquí hay un ejemplo de código que ilustra la pregunta: http://pastebin.com/zTsLpUpq. Realizar un GC después de runWith() y tomar un volcado de almacenamiento dinámico para ambas versiones indica que solo hay una instancia de artículo.

+0

Entonces, ¿cuál es la "ayuda" para GC? – Vadzim

+0

tal vez en algunos escenarios, no sé, tal vez podamos encontrar a la persona que escribió el comentario :) – milan

4

Tal vez un poco tarde, pero la explicación actual es completamente insatisfactoria para mí y creo que tengo una explicación más sensata.

En primer lugar, cada java GC realiza algún tipo de rastreo desde un conjunto raíz de una manera u otra. Esto significa que si se recolecta el encabezado anterior no leeremos la variable next de ninguna manera; no hay ninguna razón para hacerlo. Por lo tanto, IF cabeza se recoge en la siguiente iteración, no importa.

El IF en la oración anterior es la parte importante aquí. La diferencia entre la configuración al lado de algo diferente no es importante para recolectar la cabeza, pero puede hacer una diferencia para otros objetos.

Supongamos un GC generacional simple: si la cabeza está en el conjunto joven, de todos modos se recogerá en el siguiente GC. Pero si está en el conjunto anterior, solo se recopilará cuando hagamos un GC completo, lo que ocurre con poca frecuencia.

Entonces, ¿qué sucede si la cabeza está en el conjunto anterior y hacemos un GC joven?En este caso, la JVM supone que todos los objetos en el antiguo montón todavía están vivos y agrega todas las referencias de los objetos antiguos a los jóvenes al conjunto raíz del GC joven. Y eso es exactamente lo que la tarea evita aquí: escribir en el antiguo montón generalmente está protegido con una barrera de escritura o algo así para que la JVM pueda atrapar tales asignaciones y manejarlas correctamente; en nuestro caso, elimina el objeto next apuntado desde el conjunto raíz que tiene consecuencias

ejemplo corto:

Asumamos que tenemos 1 (old) -> 2 (young) -> 3 (xx). Si eliminamos 1 y 2 ahora de nuestra lista, podemos esperar que ambos elementos sean recopilados por el próximo CG. Pero si solo se produce un GC joven y NO hemos eliminado el puntero next en antiguo, no se recopilarán los elementos 1 y 2. Contrariamente a esto si hemos eliminado el puntero en 1, 2 serán recogidos por los jóvenes GC ..

+0

Realmente no veo ninguna diferencia entre lo que dices y lo que dije. También dije que no retirar el siguiente puede hacer que el GC recoja la cabeza más rápido. – Tudor

+1

@Tudor Bueno, expliqué qué mecanismo provocaría el cambio de comportamiento y bajo qué circunstancias sucedería, lo que ciertamente me parece un poco más sustancial que "algo podría hacer que el GC actúe de manera diferente". Además, no se trata de que se recopile la cabeza antes (ya que obviamente eso no tiene sentido), sino de los elementos subsiguientes, es decir, de los objetos diferentes. – Voo

+0

He afirmado en mi comentario que, a falta de la implementación real, solo puedo especular sobre lo que se supone que debe hacer esa tarea. Usted, por otro lado, ha hecho algunas afirmaciones absolutas sin proporcionar ninguna fuente y ha utilizado términos como "joven GC" sin presentarlos realmente. – Tudor

6

Si nos fijamos en el src jsr166 continuación, se encuentra el infractor commit

http://gee.cs.oswego.edu/cgi-bin/viewcvs.cgi/jsr166/src/main/java/util/concurrent/LinkedBlockingQueue.java?view=log (ver v 1.51)

esto demuestra que la respuesta es en este informe de error

http://bugs.sun.com/view_bug.do?bug_id=6805775

la discusión completa es en este hilo

http://thread.gmane.org/gmane.comp.java.jsr.166-concurrency/5758

El bit "ayuda GC" trata de evitar que las hemorragias se conserven.

Saludos

Matt