java garbage-collection puzzle java.util.concurrent

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



garbage-collection puzzle (4)

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 runWith() dinámico para ambas versiones indica que solo hay una instancia de Artículo.

¡Todas!

Encontré código extraño 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; }

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


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

1 -> 2 -> 3

Luego h apunta a la head y first a h.next :

1 -> 2 -> 3 | | h first

Luego, h.next apunta a h apunta a la first :

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

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

Habiendo dicho esto, se señaló, y estoy de acuerdo con esto, que incluso con el método clásico de dequeue (es decir, simplemente hacer first punto a la head head.next y el punto principal al first ) no hay más referencias apuntando hacia la cabeza anterior. Sin embargo, en este escenario, la cabeza anterior se deja colgando en la memoria y todavía tiene su next campo apuntando hacia la 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.


Si nos fijamos en el jrr166 src entonces encontrará el compromiso ofensivo

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

Esto muestra que la respuesta está en este informe de error

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

La discusión completa está en este hilo

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

La parte del "GC para ayudar" se trata de evitar que las hemorragias se consoliden.

Aclamaciones

Mate


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 GC de Java realiza algún tipo de rastreo desde un conjunto raíz de una forma u otra. Esto significa que si se recoge el encabezado anterior no leeremos la next variable de todos modos, no hay ninguna razón para hacerlo. Por lo tanto, si la cabeza IF 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 próximo CG. Pero si está en el conjunto anterior, solo se recopilará cuando hagamos un GC completo, lo que ocurre con poca frecuencia.

Entonces, ¿qué pasa 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 evita la tarea 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 apunta desde el conjunto raíz que tiene consecuencias

Ejemplo corto:

Supongamos 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 ocurre un GC joven y NO hemos eliminado el next puntero en antiguo, ambos elementos 1 y 2 no se recogerán. Contrario a esto, si hemos eliminado el puntero en 1, 2 será recolectado por el joven GC.