¿Cómo analizar un volcado de hilo de Java?

100

Estoy tratando de comprender más sobre Java, especialmente sobre la administración de memoria y los subprocesos. Por esta razón, recientemente he encontrado interés en buscar volcados de hilos.

Aquí hay algunas líneas tomadas de una aplicación web que usa VisualVM, una herramienta incorporada para Java:

"Finalizer" daemon prio=8 tid=0x02b3d000 nid=0x898 in Object.wait() [0x02d0f000]
   java.lang.Thread.State: WAITING (on object monitor)
    at java.lang.Object.wait(Native Method)
    - waiting on <0x27ef0288> (a java.lang.ref.ReferenceQueue$Lock)
    at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:118)
    - locked <0x27ef0288> (a java.lang.ref.ReferenceQueue$Lock)
    at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:134)
    at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:159)

   Locked ownable synchronizers:
    - None

"Reference Handler" daemon prio=10 tid=0x02b3b800 nid=0x494 in Object.wait() [0x02cbf000]
   java.lang.Thread.State: WAITING (on object monitor)
    at java.lang.Object.wait(Native Method)
    - waiting on <0x27ef0310> (a java.lang.ref.Reference$Lock)
    at java.lang.Object.wait(Object.java:485)
    at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:116)
    - locked <0x27ef0310> (a java.lang.ref.Reference$Lock)

Primero tengo preguntas sobre algunos nombres de variables:

  • ¿Qué significa tid y nid?
  • ¿Cuál es la cifra entre paréntesis cuadrados después de Object.wait?

Luego, para el seguimiento de la pila:

  • ¿Qué significa esperar en <.....> (un java.lang ....) y cuál es el número en <..>
  • ¿qué significa bloqueado <.....> (un java.lang ....) misma pregunta, qué hay en <..>

Pensé que la palabra bloqueado estaba relacionada de alguna manera con una condición de espera, sin embargo, estaba equivocado. De hecho, me pregunto por qué bloqueado se repite tres veces, pero el hilo está en estado ejecutable como se ve en el mismo volcado:

"Thread-0" prio=6 tid=0x02ee3800 nid=0xc1c runnable [0x03eaf000]
   java.lang.Thread.State: RUNNABLE
    at java.io.FileInputStream.readBytes(Native Method)
    at java.io.FileInputStream.read(FileInputStream.java:199)
    at java.io.BufferedInputStream.read1(BufferedInputStream.java:256)
    at java.io.BufferedInputStream.read(BufferedInputStream.java:317)
    - locked <0x23963378> (a java.io.BufferedInputStream)
    at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:264)
    at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:306)
    at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:158)
    - locked <0x23968450> (a java.io.InputStreamReader)
    at java.io.InputStreamReader.read(InputStreamReader.java:167)
    at java.io.BufferedReader.fill(BufferedReader.java:136)
    at java.io.BufferedReader.readLine(BufferedReader.java:299)
    - locked <0x23968450> (a java.io.InputStreamReader)
    at java.io.BufferedReader.readLine(BufferedReader.java:362)
    at org.codehaus.plexus.util.cli.StreamPumper.run(StreamPumper.java:145)

Luego, por último, este fue el peor de ellos:

"CompilerThread0" daemon prio=10 tid=0x02b81000 nid=0x698 waiting on condition [0x00000000]
   java.lang.Thread.State: RUNNABLE

Este hilo está en estado ejecutable, pero está esperando una condición. ¿Qué condición y qué es 0x00000?

¿Por qué el seguimiento de la pila es tan corto sin ninguna evidencia de la clase de subproceso?

Si pudiera responder a todas mis preguntas, estaría muy agradecido.

Gracias

Leonardo
fuente

Respuestas:

113

El TID es thead id y NID es: ID de hilo nativo. Esta identificación depende en gran medida de la plataforma. Es el NID en los volcados de subprocesos de jstack. En Windows, es simplemente el ID de subproceso a nivel del sistema operativo dentro de un proceso. En Linux y Solaris, es el PID del hilo (que a su vez es un proceso liviano). En Mac OS X, se dice que es el valor pthread_t nativo.

Vaya a este enlace: ID de subproceso a nivel de Java : para obtener una definición y una explicación más detallada de estos dos términos.

En el sitio de IBM encontré este enlace: Cómo interpretar un volcado de hilo . que cubre esto con mayor detalle:

Explica lo que significa esa espera: un bloqueo evita que más de una entidad acceda a un recurso compartido. Cada objeto en Java ™ tiene un bloqueo asociado (obtenido mediante el uso de un método o bloque sincronizado). En el caso de la JVM, los subprocesos compiten por varios recursos en la JVM y se bloquean en los objetos Java.

Luego describe el monitor como un tipo especial de mecanismo de bloqueo que se utiliza en la JVM para permitir la sincronización flexible entre subprocesos. A los efectos de esta sección, lea los términos monitor y bloqueo indistintamente.

Luego va más allá:

Para evitar tener un monitor en cada objeto, la JVM generalmente usa un indicador en una clase o bloque de método para indicar que el elemento está bloqueado. La mayoría de las veces, un fragmento de código transitará por alguna sección bloqueada sin contención. Por lo tanto, la bandera del guardián es suficiente para proteger este código. A esto se le llama monitor plano. Sin embargo, si otro hilo quiere acceder a algún código que está bloqueado, se ha producido una disputa genuina. La JVM ahora debe crear (o inflar) el objeto monitor para contener el segundo subproceso y disponer un mecanismo de señalización para coordinar el acceso a la sección de código. Este monitor ahora se llama monitor inflado.

Aquí hay una explicación más detallada de lo que está viendo en las líneas del volcado de hilo. Un hilo de Java es implementado por un hilo nativo del sistema operativo. Cada hilo está representado por una línea en negrita como:

"Hilo-1" (TID: 0x9017A0, sys_thread_t: 0x23EAC8, estado: R, ID nativo: 0x6E4) prio = 5

* Los siguientes 6 elementos explican esto, ya que los emparejé del ejemplo, valores entre corchetes []:

  1. nombre [ Thread-1 ],
  2. identificador [ 0x9017A0 ],
  3. Dirección de estructura de datos de JVM [ 0x23EAC8 ],
  4. estado actual [ R ],
  5. identificador de hilo nativo [ 0x6E4 ],
  6. y prioridad [ 5 ].

El "esperar" parece ser un subproceso de demonio asociado con el propio jvm y no el subproceso de la aplicación en sí. Cuando obtienes un "en Object.wait ()", eso significa que el hilo del demonio, "finalizador" aquí, está esperando una notificación sobre un bloqueo en un objeto, en este caso, te muestra qué notificación está esperando: "- esperando en <0x27ef0288> (un java.lang.ref.ReferenceQueue $ Lock) "

La definición de ReferenceQueue es: Colas de referencia, a las que el recolector de basura agrega objetos de referencia registrados después de que se detectan los cambios de accesibilidad apropiados.

El subproceso del finalizador se ejecuta para que la recolección de basura funcione para limpiar los recursos asociados con un objeto. Si lo veo correctamente, el finalizador no puede bloquear este objeto: java.lang.ref.ReferenceQueue.remove (ReferenceQueue.java:118) porque el objeto java está ejecutando un método, por lo que el hilo del finalizador es bloqueado hasta que ese objeto termine con su tarea actual.

Además, el finalizador no solo busca recuperar memoria, está más involucrado que eso para limpiar recursos. Necesito estudiar más al respecto, pero si tiene archivos abiertos, sockets, etc. relacionados con los métodos de un objeto, entonces el finalizador también trabajará para liberar esos elementos.

¿Cuál es la cifra entre paréntesis cuadrados después de Object.wait en el volcado de hilo?

Es un puntero en la memoria al hilo. Aquí hay una descripción más detallada:

C.4.1 Información del hilo

La primera parte de la sección del hilo muestra el hilo que provocó el error fatal, de la siguiente manera:

Current thread (0x0805ac88):  JavaThread "main" [_thread_in_native, id=21139]
                    |             |         |            |          +-- ID
                    |             |         |            +------------- state
                    |             |         +-------------------------- name
                    |             +------------------------------------ type
                    +-------------------------------------------------- pointer

El puntero de hilo es el puntero a la estructura de hilo interno de Java VM. Por lo general, no tiene interés a menos que esté depurando una máquina virtual Java en vivo o un archivo central.

Esta última descripción proviene de: Guía de solución de problemas para Java SE 6 con HotSpot VM

Aquí hay algunos enlaces más sobre volcados de subprocesos:

James Drinkard
fuente
11

Además de la excelente respuesta de @James Drinkard:

Tenga en cuenta que, dependiendo de la implementación subyacente, el java.lang.Thread.State de un hilo que está bloqueado en un método nativo se puede informar como RUNNABLE, dondeA thread in the runnable state is executing in the Java virtual machine but it may be waiting for other resources from the operating system such as processor.

Resulta que esta descripción también abarca el bloqueo en una llamada al sistema operativo, como una operación de sondeo o lectura, presumiblemente porque no hay garantía de que la JVM pueda saber cuándo se ha bloqueado una llamada al método nativo a nivel del sistema operativo.

Muchas discusiones sobre volcados de subprocesos de JVM que he visto ignoran esta posibilidad por completo o la pasan por alto alegremente sin considerar las implicaciones, entre las que se destaca que las herramientas de monitoreo pueden informar de manera confusa que varios de estos subprocesos se están 'ejecutando', y además que todos funcionan al 100%.

Jeremy
fuente