Cómo analizar el volcado de subprocesos

Hay bastantes temas sobre los aspectos internos de la JVM en el programa del curso Java Developer . Entendemos los mecanismos de trabajo de las colecciones, bytecode, recolectores de basura, etc. Hoy ofrecemos su atención a la traducción de un artículo bastante interesante sobre el volcado de hilos. Qué es, cómo obtenerlo y cómo usarlo.

¿Quieres aprender a analizar el volcado de subprocesos? Ve debajo del gato para obtener más información sobre cómo obtener volcado de subprocesos en Java y qué hacer con él más adelante.

La mayoría de las aplicaciones Java modernas son multiproceso. El subprocesamiento múltiple puede expandir significativamente la funcionalidad de la aplicación, al mismo tiempo que introduce una complejidad significativa.

En una aplicación de subproceso único, todos los recursos (memoria compartida, operaciones de entrada / salida, etc.) se pueden usar sin sincronización, porque en un momento dado, solo un subproceso usa el recurso.

En el caso de aplicaciones de subprocesos múltiples, es necesario encontrar un compromiso entre complicar el programa y un posible aumento en el rendimiento, cuando varios subprocesos pueden usar todos los procesadores (CPU) disponibles (a menudo más de un) núcleo. Si todo se hace correctamente, utilizando el subprocesamiento múltiple (formalizado en la Ley de Amdahl ), puede lograr un aumento significativo en el rendimiento de la aplicación. Sin embargo, uno debe recordar proporcionar acceso simultáneo de varias corrientes a un recurso compartido. En la mayoría de los casos, los marcos como Spring encapsulan el trabajo con hilos y ocultan muchos detalles técnicos de los usuarios. Sin embargo, en el caso de utilizar marcos modernos y complejos, algo puede salir mal, y nosotros, como usuarios, encontraremos errores de subprocesamiento múltiple difíciles de resolver.

Afortunadamente, Java está equipado con un mecanismo especial para obtener información sobre el estado actual de todos los subprocesos en un momento dado: es un volcado de subprocesos (una especie de instantánea). En este artículo, aprenderemos cómo obtener un volcado de subprocesos para una aplicación de tamaño realista y cómo analizar este volcado.

Se supone que el lector tiene información básica sobre programación multiproceso y es consciente de los problemas de sincronización de subprocesos y el uso de recursos compartidos. Sin embargo, no será superfluo actualizar algunos términos y conceptos básicos.

Terminología básica


A primera vista, los volcados de hilos de Java pueden parecer una "letra china", los siguientes conceptos son clave para entenderlo. En general, repitamos los términos básicos de subprocesamiento múltiple, que usaremos para analizar los volcados.

  • Subproceso o subproceso es una unidad de subprocesamiento múltiple discreta administrada por la máquina virtual Java (JVM). Los subprocesos JVM corresponden a subprocesos en el sistema operativo (SO): subprocesos nativos que implementan el mecanismo de ejecución de código.

    Cada hilo tiene un identificador y nombre únicos. Las corrientes pueden ser "demonios" y "no demonios".

    El programa finaliza cuando finalizan todos los subprocesos no daemon o cuando se llama al método Runtime.exit . Los "demonios" que trabajan no afectan la finalización del programa. Es decir La JVM está esperando que todos los "no demonios" se finalicen y se apaguen; no están prestando atención a los "no demonios".

    Para obtener más información, consulte la documentación de la clase Thread .
    Una secuencia puede estar en uno de los siguientes estados:

    • Subproceso vivo o "en vivo": un subproceso que funciona (estado normal).
    • Hilo bloqueado o "bloqueado": un hilo que intentó ingresar a la sección sincronizada (sincronizado), pero otro hilo ya logró ingresar primero a este bloque, y todos los siguientes hilos que intentan ingresar al mismo bloque están bloqueados.
    • Subproceso en espera o "en espera": un subproceso que llamó al método de espera (posiblemente con un tiempo de espera excedido) y ahora está esperando que otro método ejecute notificar o notificar a todos en el mismo objeto.

      Tenga en cuenta que el hilo no se considera "en espera" si se llama esperar con un tiempo de espera y este tiempo de espera ha expirado.
    • Hilo para dormir o "dormir": un hilo que no se está ejecutando actualmente, porque realizó el método Thread.sleep (que indica la duración del "sueño").
  • Monitor es un mecanismo utilizado por la JVM para proporcionar acceso multiproceso a un solo objeto. El mecanismo se inicia utilizando la palabra clave especial sincronizada .

    Cada objeto en Java tiene un monitor con el que se puede sincronizar el hilo, es decir establecer un bloqueo, lo que garantiza que ningún otro hilo accederá a este objeto hasta que se libere el bloqueo, es decir thread: el propietario del bloqueo no saldrá del bloque sincronizado .

    Consulte la sección Sincronización (17.1) de la Especificación de lenguaje Java (JLS) para obtener más información .
  • El punto muerto es una situación en la que un hilo, digamos A, bloquea un recurso, necesita otro recurso que esté bloqueado por otro hilo, digamos B. La corriente B no libera este recurso, porque Para completar una determinada operación, necesita un recurso que esté bloqueado por el subproceso A. Resulta que el subproceso A está esperando que el subproceso B desbloquee el recurso, que está esperando que otro subproceso sea desbloqueado por el subproceso A. Y, por lo tanto, los subprocesos se esperan entre sí. Como resultado, todo el programa se cuelga y espera a que los hilos se desbloqueen de alguna manera y continúen funcionando. Puede haber muchos hilos en un punto muerto. Este problema es bien conocido como el "Problema de los filósofos gastronómicos" .


  • Livelock es una situación en la que el subproceso A obliga al subproceso B a realizar alguna acción, lo que a su vez hace que el subproceso A ejecute la acción original, lo que una vez más provoca la acción del subproceso B. Se obtiene una dependencia cíclica. Esto se puede imaginar como un perro corriendo tras su cola. De manera similar a Deadlock , en una situación de Livelock, el programa no avanza, es decir. no realiza una acción útil, sin embargo, en esta situación, los hilos no están bloqueados.

La terminología presentada no es exhaustiva para describir el mundo de los subprocesos múltiples, pero es suficiente para comenzar a analizar los volcados de subprocesos.

Se puede encontrar información más detallada en estas fuentes:

Sección 17 de la concurrencia JLS y Java en la práctica

Usando estos conceptos simples sobre el flujo en Java, podemos crear una aplicación de prueba. Para esta aplicación compilaremos volcado de subprocesos. Analizaremos el volcado resultante y extraeremos información útil sobre los flujos de aplicaciones actuales.

Crear un programa de muestra


Antes de crear un volcado de subprocesos, necesitamos desarrollar una aplicación Java. El tradicional "hola, mundo!" demasiado simple para nuestro propósito, y un volcado de tamaño medio de la aplicación puede ser demasiado complicado para demostrarlo. En base a esto, crearemos una aplicación bastante simple en la que se crean dos hilos. Y los hilos se estancan:

public class DeadlockProgram { public static void main(String[] args) throws Exception { Object resourceA = new Object(); Object resourceB = new Object(); Thread threadLockingResourceAFirst = new Thread(new DeadlockRunnable(resourceA, resourceB)); Thread threadLockingResourceBFirst = new Thread(new DeadlockRunnable(resourceB, resourceA)); threadLockingResourceAFirst.start(); Thread.sleep(500); threadLockingResourceBFirst.start(); } private static class DeadlockRunnable implements Runnable { private final Object firstResource; private final Object secondResource; public DeadlockRunnable(Object firstResource, Object secondResource) { this.firstResource = firstResource; this.secondResource = secondResource; } @Override public void run() { try { synchronized(firstResource) { printLockedResource(firstResource); Thread.sleep(1000); synchronized(secondResource) { printLockedResource(secondResource); } } } catch (InterruptedException e) { System.out.println("Exception occurred: " + e); } } private static void printLockedResource(Object resource) { System.out.println(Thread.currentThread().getName() + ": locked resource -> " + resource); } } } 

Este programa crea dos recursos: resourceA y resourceB, e inicia dos hilos: threadLockingResourceAFirst y threadLockingResourceBFirst, que bloquean los recursos del otro.

La causa del punto muerto es un bloqueo "cruzado" de recursos por hilos.

La razón de la existencia de un punto muerto es un intento de "mutuamente" aprovechar los recursos, es decir El hilo threadLockingResourceAFirst captura el recurso resourceA, el hilo threadLockingResourceBFirst captura el recurso resourceB. Después de eso, threadLockingResourceAFirst, sin liberar su recurso, intenta capturar resourceB, y threadLockingResourceBFirst, sin liberar su recurso, intenta capturar resourceA. Como resultado, los hilos están bloqueados. Se ha agregado un retraso de 1s para garantizar el bloqueo. Los hilos esperan la liberación de los recursos necesarios, pero esto nunca sucederá.

El resultado del programa será así (los números después de java.lang.Object @ serán diferentes para cada lanzamiento):

 Thread-0: locked resource -> java.lang.Object@149bc794 Thread-1: locked resource -> java.lang.Object@17c10009 

Después de la salida de estos mensajes, el programa parecerá que se está ejecutando (el proceso que ejecuta este programa no se ha completado), mientras que el programa no realiza ningún trabajo. Así es como se ve un punto muerto en la práctica. Para resolver el problema, necesitamos crear manualmente un volcado de banda de rodadura y analizar el estado de los hilos.

Generación de volcado de subprocesos


En la práctica, un programa Java puede bloquearse al crear un volcado de subprocesos. Sin embargo, en algunos casos (por ejemplo, en el caso de puntos muertos), el programa no finaliza y el volcado de subprocesos no se crea, simplemente se bloquea. Para crear un volcado de dichos programas bloqueados, en primer lugar, debe encontrar el identificador del proceso del programa, es decir, ID de proceso (PID). Para hacer esto, puede utilizar la utilidad JVM Process Status (JPS), que, a partir de la versión 7, forma parte del Java Development Kit (JDK). Para encontrar el PID de proceso de nuestro programa bloqueado, simplemente ejecutamos jps en el terminal (Windows o Linux):

 $ jps 11568 DeadlockProgram 15584 Jps 15636 

La primera columna es el identificador de la máquina virtual local (ID de máquina virtual local, es decir, lvmid) para el proceso Java en ejecución. En el contexto de la JVM local, lvmid apunta al PID del proceso Java.

Cabe señalar que es probable que este valor difiera del valor anterior. La segunda columna es el nombre de la aplicación, que puede apuntar al nombre de la clase principal, archivo jar o igual a "Desconocido". Todo depende de cómo se inició la aplicación.

En nuestro caso, el nombre de la aplicación DeadlockProgram es el nombre de las clases principales que se iniciaron cuando se inició el programa. En el ejemplo anterior PID del programa 11568, esta información es suficiente para generar un volcado de subprocesos. Para generar el volcado, utilizaremos la utilidad jstack , que es parte del JDK, comenzando con la versión 7. Para obtener el volcado, pasaremos el PID de nuestro programa a jstack y especificaremos el indicador -l (creando una lista larga). La salida de la utilidad se redirigirá a un archivo de texto, es decir thread_dump.txt:

 jstack -l 11568 > thread_dump.txt 

El archivo resultante thread_dump.txt contiene el volcado de subprocesos de nuestro programa bloqueado y contiene información importante para diagnosticar las causas del punto muerto.

Si el JDK se usa hasta la versión 7, para generar un volcado, puede usar la utilidad Linux: matar con el indicador -3. Llamar a kill -3 enviará al programa una señal SIGQUIT.

En nuestro caso, la llamada será así:

 kill -3 11568 

Análisis simple de volcado de subprocesos


Al abrir el archivo thread_dump.txt, veremos algo como lo siguiente:

 2018-06-19 16:44:44
 Full thread dump Java HotSpot (TM) VM de servidor de 64 bits (10.0.1 + 10 modo mixto):
 Información de clase SMR de subprocesos
 _java_thread_list = 0x00000250e5488a00, longitud = 13, elementos = {
 0x00000250e4979000, 0x00000250e4982800, 0x00000250e52f2800, 0x00000250e4992800,
 0x00000250e4995800, 0x00000250e49a5800, 0x00000250e49ae800, 0x00000250e5324000,
 0x00000250e54cd800, 0x00000250e54cf000, 0x00000250e54d1800, 0x00000250e54d2000,
 0x00000250e54d0800
 }
 "Controlador de referencia" # 2 daemon prio = 10 os_prio = 2 tid = 0x00000250e4979000 nid = 0x3c28 esperando en condición [0x000000b82a9ff000]
    java.lang.Thread.State: RUNNABLE
     en java.lang.ref.Reference.waitForReferencePendingList (java.base@10.0.1/Native Method)
     en java.lang.ref.Reference.processPendingReferences (java.base@10.0.1/Reference.java: 174)
     en java.lang.ref.Reference.access $ 000 (java.base@10.0.1/Reference.java: 44)
     en java.lang.ref.Reference $ ReferenceHandler.run (java.base@10.0.1/Reference.java: 138)
    Sincronizadores de propiedad bloqueados:
     - ninguno
 "Finalizador" # 3 daemon prio = 8 os_prio = 1 tid = 0x00000250e4982800 nid = 0x2a54 en Object.wait () [0x000000b82aaff000]
    java.lang.Thread.State: WAITING (en el monitor de objetos)
     en java.lang.Object.wait (java.base@10.0.1/Native Method)
     - esperando en <0x0000000089509410> (un java.lang.ref.ReferenceQueue $ Lock)
     en java.lang.ref.ReferenceQueue.remove (java.base@10.0.1/ReferenceQueue.java: 151)
     - esperando para volver a bloquear en wait () <0x0000000089509410> (a java.lang.ref.ReferenceQueue $ Lock)
     en java.lang.ref.ReferenceQueue.remove (java.base@10.0.1/ReferenceQueue.java: 172)
     en java.lang.ref.Finalizer $ FinalizerThread.run (java.base@10.0.1/Finalizer.java: 216)
    Sincronizadores de propiedad bloqueados:
     - ninguno
 "Despachador de señal" # 4 daemon prio = 9 os_prio = 2 tid = 0x00000250e52f2800 nid = 0x2184 ejecutable [0x0000000000000000]
    java.lang.Thread.State: RUNNABLE
    Sincronizadores de propiedad bloqueados:
     - ninguno
 "Attach Listener" # 5 daemon prio = 5 os_prio = 2 tid = 0x00000250e4992800 nid = 0x1624 esperando en condición [0x0000000000000000]
    java.lang.Thread.State: RUNNABLE
    Sincronizadores de propiedad bloqueados:
     - ninguno
 "C2 CompilerThread0" # 6 daemon prio = 9 os_prio = 2 tid = 0x00000250e4995800 nid = 0x4198 esperando en condición [0x0000000000000000]
    java.lang.Thread.State: RUNNABLE
    Sin tarea de compilación
    Sincronizadores de propiedad bloqueados:
     - ninguno
 "C2 CompilerThread1" # 7 daemon prio = 9 os_prio = 2 tid = 0x00000250e49a5800 nid = 0x3b98 esperando en condición [0x0000000000000000]
    java.lang.Thread.State: RUNNABLE
    Sin tarea de compilación
    Sincronizadores de propiedad bloqueados:
     - ninguno
 "C1 CompilerThread2" # 8 daemon prio = 9 os_prio = 2 tid = 0x00000250e49ae800 nid = 0x1a84 esperando en condición [0x0000000000000000]
    java.lang.Thread.State: RUNNABLE
    Sin tarea de compilación
    Sincronizadores de propiedad bloqueados:
     - ninguno
 "Thread Sweeper" # 9 daemon prio = 9 os_prio = 2 tid = 0x00000250e5324000 nid = 0x5f0 ejecutable [0x0000000000000000]
    java.lang.Thread.State: RUNNABLE
    Sincronizadores de propiedad bloqueados:
     - ninguno
 "Hilo de servicio" # 10 daemon prio = 9 os_prio = 0 tid = 0x00000250e54cd800 nid = 0x169c ejecutable [0x0000000000000000]
    java.lang.Thread.State: RUNNABLE
    Sincronizadores de propiedad bloqueados:
     - ninguno
 "Common-Cleaner" # 11 daemon prio = 8 os_prio = 1 tid = 0x00000250e54cf000 nid = 0x1610 en Object.wait () [0x000000b82b2fe000]
    java.lang.Thread.State: TIMED_WAITING (en el monitor de objetos)
     en java.lang.Object.wait (java.base@10.0.1/Native Method)
     - esperando en <0x000000008943e600> (a java.lang.ref.ReferenceQueue $ Lock)
     en java.lang.ref.ReferenceQueue.remove (java.base@10.0.1/ReferenceQueue.java: 151)
     - esperando para volver a bloquear en wait () <0x000000008943e600> (a java.lang.ref.ReferenceQueue $ Lock)
     en jdk.internal.ref.CleanerImpl.run (java.base@10.0.1/CleanerImpl.java: 148)
     en java.lang.Thread.run (java.base@10.0.1/Thread.java: 844)
     en jdk.internal.misc.InnocuousThread.run (java.base@10.0.1/InnocuousThread.java: 134)
    Sincronizadores de propiedad bloqueados:
     - ninguno
 "Thread-0" # 12 prio = 5 os_prio = 0 tid = 0x00000250e54d1800 nid = 0xdec esperando la entrada del monitor [0x000000b82b4ff000]
    java.lang.Thread.State: BLOCKED (en el monitor de objetos)
     en DeadlockProgram $ DeadlockRunnable.run (DeadlockProgram.java:34)
     - esperando para bloquear <0x00000000894465b0> (un java.lang.Object)
     - bloqueado <0x00000000894465a0> (un java.lang.Object)
     en java.lang.Thread.run (java.base@10.0.1/Thread.java: 844)
    Sincronizadores de propiedad bloqueados:
     - ninguno
 "Thread-1" # 13 prio = 5 os_prio = 0 tid = 0x00000250e54d2000 nid = 0x415c esperando la entrada del monitor [0x000000b82b5ff000]
    java.lang.Thread.State: BLOCKED (en el monitor de objetos)
     en DeadlockProgram $ DeadlockRunnable.run (DeadlockProgram.java:34)
     - esperando para bloquear <0x00000000894465a0> (un java.lang.Object)
     - bloqueado <0x00000000894465b0> (un java.lang.Object)
     en java.lang.Thread.run (java.base@10.0.1/Thread.java: 844)
    Sincronizadores de propiedad bloqueados:
     - ninguno
 "DestroyJavaVM" # 14 prio = 5 os_prio = 0 tid = 0x00000250e54d0800 nid = 0x2b8c esperando en condición [0x0000000000000000]
    java.lang.Thread.State: RUNNABLE
    Sincronizadores de propiedad bloqueados:
     - ninguno
 "VM Thread" os_prio = 2 tid = 0x00000250e496d800 nid = 0x1920 ejecutable  
 "Tema de GC # 0" os_prio = 2 tid = 0x00000250c35b5800 nid = 0x310c ejecutable  
 "GC Thread # 1" os_prio = 2 tid = 0x00000250c35b8000 nid = 0x12b4 ejecutable  
 "GC Thread # 2" os_prio = 2 tid = 0x00000250c35ba800 nid = 0x43f8 ejecutable  
 "GC Thread # 3" os_prio = 2 tid = 0x00000250c35c0800 nid = 0x20c0 ejecutable  
 "Marcador principal G1" os_prio = 2 tid = 0x00000250c3633000 nid = 0x4068 ejecutable  
 "G1 Conc # 0" os_prio = 2 tid = 0x00000250c3636000 nid = 0x3e28 ejecutable  
 "G1 Refine # 0" os_prio = 2 tid = 0x00000250c367e000 nid = 0x3c0c ejecutable  
 "G1 Refine # 1" os_prio = 2 tid = 0x00000250e47fb800 nid = 0x3890 ejecutable  
 "G1 Refine # 2" os_prio = 2 tid = 0x00000250e47fc000 nid = 0x32a8 ejecutable  
 "G1 Refine # 3" os_prio = 2 tid = 0x00000250e47fd800 nid = 0x3d00 ejecutable  
 "Muestreo RemSet Young G1" os_prio = 2 tid = 0x00000250e4800800 nid = 0xef4 ejecutable  
 "Subproceso de tarea periódica de VM" os_prio = 2 tid = 0x00000250e54d6800 nid = 0x3468 esperando en condición  
 Referencias globales de JNI: 2
 Encontrado un punto muerto de nivel Java:
 ===============================
 "Hilo-0":
   esperando para bloquear el monitor 0x00000250e4982480 (objeto 0x00000000894465b0, un java.lang.Object),
   que está en manos de "Thread-1"
 "Hilo-1":
   esperando para bloquear el monitor 0x00000250e4982380 (objeto 0x00000000894465a0, un java.lang.Object),
   que está en manos de "Thread-0"
 Información de la pila de Java para los hilos enumerados anteriormente:
 =================================================== =
 "Hilo-0":
     en DeadlockProgram $ DeadlockRunnable.run (DeadlockProgram.java:34)
     - esperando para bloquear <0x00000000894465b0> (un java.lang.Object)
     - bloqueado <0x00000000894465a0> (un java.lang.Object)
     en java.lang.Thread.run (java.base@10.0.1/Thread.java: 844)
 "Hilo-1":
     en DeadlockProgram $ DeadlockRunnable.run (DeadlockProgram.java:34)
     - esperando para bloquear <0x00000000894465a0> (un java.lang.Object)
     - bloqueado <0x00000000894465b0> (un java.lang.Object)
     en java.lang.Thread.run (java.base@10.0.1/Thread.java: 844)
 Encontrado 1 punto muerto.

Información introductoria


Aunque a primera vista este archivo puede parecer demasiado complicado y confuso, en realidad es bastante simple si lo desmonta en partes paso a paso.

La primera línea indica el momento en que se formó el volcado, la segunda: información de diagnóstico sobre la JVM, en la que se recibió el volcado:

 2018-06-19 16:44:44 Full thread dump Java HotSpot(TM) 64-Bit Server VM (10.0.1+10 mixed mode): 

No hay información de flujo en esta sección. Aquí se establece el contexto general del sistema en el que se recopiló el volcado.

Información general de flujo


La siguiente sección proporciona información sobre los subprocesos que se estaban ejecutando en el sistema en el momento de la recopilación de volcados:

 Información de SMR de clase de subprocesos:
 _java_thread_list = 0x00000250e5488a00, longitud = 13, elementos = {
 0x00000250e4979000, 0x00000250e4982800, 0x00000250e52f2800, 0x00000250e4992800,
 0x00000250e4995800, 0x00000250e49a5800, 0x00000250e49ae800, 0x00000250e5324000,
 0x00000250e54cd800, 0x00000250e54cf000, 0x00000250e54d1800, 0x00000250e54d2000,
 0x00000250e54d0800
 }

La siguiente sección enumera:

Información de recuperación de memoria segura (SMR)

Contiene información sobre hilos fuera de la JVM, es decir. estos no son hilos de máquinas virtuales o hilos de recolección de basura. Si observa las direcciones de estos subprocesos, notará que corresponden al valor de tid : la dirección “natural, de hierro” (nativa) en el sistema operativo y no la ID de subproceso.

Las elipses se utilizan para ocultar información redundante:

 "Controlador de referencia" # 2 ... tid = 0x00000250e4979000 ...
 "Finalizador" # 3 ... tid = 0x00000250e4982800 ...
 "Despachador de señal" # 4 ... tid = 0x00000250e52f2800 ...
 "Adjuntar escucha" # 5 ... tid = 0x00000250e4992800 ...
 "C2 CompilerThread0" # 6 ... tid = 0x00000250e4995800 ...
 "C2 CompilerThread1" # 7 ... tid = 0x00000250e49a5800 ...
 "C1 CompilerThread2" # 8 ... tid = 0x00000250e49ae800 ...
 "Hilo de barrido" # 9 ... tid = 0x00000250e5324000 ...
 "Hilo de servicio" # 10 ... tid = 0x00000250e54cd800 ...
 "Limpiador común" # 11 ... tid = 0x00000250e54cf000 ...
 "Hilo-0" # 12 ... tid = 0x00000250e54d1800 ...
 "Hilo-1" # 13 ... tid = 0x00000250e54d2000 ...
 "DestroyJavaVM" # 14 ... tid = 0x00000250e54d0800 ...

Corrientes


Justo después del bloque SMR hay una lista de hilos. El primer hilo de nuestra lista es el controlador de referencia:

 "Controlador de referencia" # 2 daemon prio = 10 os_prio = 2 tid = 0x00000250e4979000 nid = 0x3c28 esperando en condición [0x000000b82a9ff000]
    java.lang.Thread.State: RUNNABLE
     en java.lang.ref.Reference.waitForReferencePendingList (java.base@10.0.1/Native Method)
     en java.lang.ref.Reference.processPendingReferences (java.base@10.0.1/Reference.java: 174)
     en java.lang.ref.Reference.access $ 000 (java.base@10.0.1/Reference.java: 44)
     en java.lang.ref.Reference $ ReferenceHandler.run (java.base@10.0.1/Reference.java: 138)
    Sincronizadores de propiedad bloqueados:
     - ninguno

Breve descripción de la transmisión.


La primera línea para cada hilo proporciona una descripción general. La descripción contiene los siguientes elementos:
SeccionEjemploDescripción
Nombre"Controlador de referencia"Nombre de secuencia legible para humanos. El nombre se puede especificar llamando al método setName del objeto Thread . Y recibe una llamada a getName
ID# 2Una identificación única asignada a cada objeto de la clase Thread . ID se genera para subprocesos en el sistema. El valor inicial es 1. A cada subproceso recién creado se le asigna su propia ID, previamente aumentada en 1. Esta propiedad de subproceso de solo lectura se puede obtener mediante la función getId de un objeto de la clase Subproceso .
Estado del demoniodemonioLa bandera es una señal de que el hilo es un demonio. Si es un demonio, se establecerá la bandera. Por ejemplo, el hilo -0 no es un demonio.
Prioridadprio = 10La prioridad numérica de la secuencia de Java. Tenga en cuenta que esta prioridad no se corresponde necesariamente con la prioridad del subproceso asociado en el sistema operativo. Para establecer la prioridad, puedes
use el método setPriority de un objeto de clase Thread , y para obtener
Método getPriority .
Prioridad de subproceso del sistema operativoos_prio = 2Tema prioritario en el sistema operativo. Esta prioridad puede diferir de la asignada al hilo de Java vinculado.
Direccióntid = 0x00000250e4979000La dirección de la secuencia de Java. Esta dirección es un puntero al objeto nativo de la Interfaz nativa de Java (JNI) de la clase Thread (un objeto Thread C ++ que está conectado al hilo Java a través de JNI). Este valor se obtiene al lanzar un puntero a este
(el objeto C ++ que está asociado con este hilo de Java) a entero. Ver
línea 879 en hotspot / share / runtime / thread.cpp :
 st-> print ("tid =" INTPTR_FORMAT "", p2i (este));

Aunque la clave para este objeto ( tid ) puede parecer una ID de flujo,
de hecho, esta es la dirección del objeto conectado JNI C ++ Thread , y este no es el valor que
devuelve el método getId del hilo Java.
ID de subproceso del sistema operativonid = 0x3c28El identificador único del subproceso del sistema operativo al que está enlazado el subproceso Java.
Este valor se genera con el siguiente código:
línea 42 en hotspot / share / runtime / osThread.cpp :
 st-> print ("nid = 0x% x", thread_id ());

Estadoesperando en condiciónEstado legible por humanos del hilo actual.
Esta línea muestra información adicional sobre el estado simple de la transmisión (ver más abajo), que puede ser
solía entender lo que iba a hacer el hilo (es decir, si el hilo intentaba obtener un bloqueo
o esperando que se cumpla la condición de desbloqueo).
Último puntero de pila Java conocido[0x000000b82a9ff000]El último puntero de pila (SP) conocido asociado con esta secuencia.
Este valor se obtiene usando código nativo de C ++ mezclado con código Java usando JNI. El valor es devuelto por la función last_Java_sp () ,
línea 2886 en hotspot / share / runtime / thread.cpp :
   st-> print_cr ("[" INTPTR_FORMAT "]", 
     (intptr_t) last_Java_sp () & ~ right_n_bits (12));

Para volcados de subprocesos simples, esta información es casi inútil. Sin embargo, en casos complejos, SP puede
ser utilizado para rastrear cerraduras.

Estado de la transmisión


La segunda línea es el estado actual de la secuencia. Los posibles estados de flujo se enumeran en enum:
Hilo Estado :

Nuevo
Ejecutable
BLOQUEADO
ESPERANDO
TIMED_WAITING
TERMINADO

Consulte la documentación para más detalles.

Hilo de pila de seguimiento


La siguiente sección contiene el seguimiento de la pila de la secuencia en el momento en que se realizó el volcado. Esta traza de pila es muy similar a una traza de pila, que se produce por una excepción no detectada. Y contiene los nombres de clases y cadenas que se ejecutaron en el momento en que se formó el volcado. En el caso de la secuencia del controlador de referencia, no vemos nada interesante.

Sin embargo, hay algo interesante sobre el rastreo de subprocesos Thread-02 que es diferente del rastreo estándar:

 "Thread-0" # 12 prio = 5 os_prio = 0 tid = 0x00000250e54d1800 nid = 0xdec esperando la entrada del monitor [0x000000b82b4ff000]
    java.lang.Thread.State: BLOCKED (en el monitor de objetos)
     en DeadlockProgram $ DeadlockRunnable.run (DeadlockProgram.java:34)
     - esperando para bloquear <0x00000000894465b0> (un java.lang.Object)
     - bloqueado <0x00000000894465a0> (un java.lang.Object)
     en java.lang.Thread.run (java.base@10.0.1/Thread.java: 844)
    Sincronizadores de propiedad bloqueados:
     - ninguno

En el seguimiento, vemos que se ha agregado información sobre el bloqueo. Este hilo espera un bloqueo en el objeto con la dirección 0x00000000894465b0 (tipo de objeto java.lang.Object). Además, el hilo en sí mismo mantiene el bloqueo con la dirección 0x00000000894465a0 (también un objeto java.lang.Object). Esta información nos será útil más adelante para el diagnóstico de punto muerto.

Primitivas de sincronización capturadas (Sincronizador propietario)


La última sección enumera las primitivas de sincronización capturadas por la secuencia. Estos son objetos que se pueden usar para sincronizar hilos, por ejemplo, bloqueos.

De acuerdo con la documentación oficial de Java, Ownable Synchronizer es descendiente de AbstractOwnableSynchronizer (o su subclase), que el flujo puede capturar exclusivamente para la sincronización.

ReentrantLock y write-lock , pero no el read-lock de la clase ReentrantReadWriteLock son dos buenos ejemplos de tales "sincronizadores que se pueden usar" ofrecidos por la plataforma.

Para obtener más información sobre este tema, puede consultar este
Publicar

Subprocesos JVM


La siguiente sección del volcado contiene información sobre subprocesos técnicos JVM que no son parte de la aplicación y están asociados con subprocesos del sistema operativo. Porque Estos flujos funcionan fuera de la aplicación, no tienen identificadores de flujo. Muy a menudo, estos son hilos de recolección de basura y otros hilos técnicos de JVM:

 "VM Thread" os_prio = 2 tid = 0x00000250e496d800 nid = 0x1920 ejecutable  
 "Tema de GC # 0" os_prio = 2 tid = 0x00000250c35b5800 nid = 0x310c ejecutable  
 "GC Thread # 1" os_prio = 2 tid = 0x00000250c35b8000 nid = 0x12b4 ejecutable  
 "GC Thread # 2" os_prio = 2 tid = 0x00000250c35ba800 nid = 0x43f8 ejecutable  
 "GC Thread # 3" os_prio = 2 tid = 0x00000250c35c0800 nid = 0x20c0 ejecutable  
 "Marcador principal G1" os_prio = 2 tid = 0x00000250c3633000 nid = 0x4068 ejecutable  
 "G1 Conc # 0" os_prio = 2 tid = 0x00000250c3636000 nid = 0x3e28 ejecutable  
 "G1 Refine # 0" os_prio = 2 tid = 0x00000250c367e000 nid = 0x3c0c ejecutable  
 "G1 Refine # 1" os_prio = 2 tid = 0x00000250e47fb800 nid = 0x3890 ejecutable  
 "G1 Refine # 2" os_prio = 2 tid = 0x00000250e47fc000 nid = 0x32a8 ejecutable  
 "G1 Refine # 3" os_prio = 2 tid = 0x00000250e47fd800 nid = 0x3d00 ejecutable  
 "Muestreo RemSet Young G1" os_prio = 2 tid = 0x00000250e4800800 nid = 0xef4 ejecutable  
 "Subproceso de tarea periódica de VM" os_prio = 2 tid = 0x00000250e54d6800 nid = 0x3468 esperando en condición

Enlaces globales de JNI


Esta sección indica el número de referencias globales utilizadas por la JVM a través de la JNI. Estos enlaces no son atendidos por el recolector de basura y pueden causar una pérdida de memoria en ciertas circunstancias.

 Referencias globales de JNI: 2

En la mayoría de los casos simples, esta información no se utiliza. Sin embargo, se debe entender la importancia de las referencias globales. Vea esta publicación para más detalles.

Hilos estancados


La última sección contiene información sobre puntos muertos encontrados.
Si no se encuentran, la sección estará vacía. Porque Desarrollamos específicamente una aplicación con bloqueos, en nuestro caso esta sección es. Se detectó un bloqueo durante el volcado y se le presenta el siguiente mensaje:

 Encontrado un punto muerto de nivel Java:
 ===============================
 "Hilo-0":
   esperando para bloquear el monitor 0x00000250e4982480 (objeto 0x00000000894465b0, un java.lang.Object),
   que está en manos de "Thread-1"
 "Hilo-1":
   esperando para bloquear el monitor 0x00000250e4982380 (objeto 0x00000000894465a0, un java.lang.Object),
   que está en manos de "Thread-0"
 Información de la pila de Java para los hilos enumerados anteriormente:
 =================================================== =
 "Hilo-0":
     en DeadlockProgram $ DeadlockRunnable.run (DeadlockProgram.java:34)
     - esperando para bloquear <0x00000000894465b0> (un java.lang.Object)
     - bloqueado <0x00000000894465a0> (un java.lang.Object)
     en java.lang.Thread.run (java.base@10.0.1/Thread.java: 844)
 "Hilo-1":
     en DeadlockProgram $ DeadlockRunnable.run (DeadlockProgram.java:34)
     - esperando para bloquear <0x00000000894465a0> (un java.lang.Object)
     - bloqueado <0x00000000894465b0> (un java.lang.Object)
     en java.lang.Thread.run (java.base@10.0.1/Thread.java: 844)
 Encontrado 1 punto muerto.

La primera subsección describe el escenario de punto muerto:

Thread-0 espera poder capturar el monitor (este es un acceso al bloque sincronizado (secondResource) en nuestra aplicación), al mismo tiempo, este hilo tiene un monitor que intenta capturar el Thread-1 (está accediendo al mismo fragmento de código: sincronizado (secondResource ) en nuestra aplicación).

Este bloqueo circular también se llama punto muerto . En la imagen de abajo
Esta situación se presenta en forma gráfica:



En la segunda subsección, se da seguimiento de pila para ambos subprocesos bloqueados.

Este seguimiento de la pila nos permite rastrear el funcionamiento de cada subproceso hasta que se produce un bloqueo.
En nuestro caso, si miramos la línea:

en DeadlockProgram $ DeadlockRunnable.run (DeadlockProgram.java:34) , luego veremos la parte del problema del código:

 printLockedResource (secondResource);

Esta línea es la primera línea del bloque sincronizado, que es la razón del bloqueo, y nos dice que la sincronización en el segundo recurso es la razón del bloqueo mutuo. Para remediar la situación, debemos asegurarnos de que ambos hilos tengan el mismo orden de sincronización en los recursos resourceA y resourceB. Si hacemos esto, llegaremos a la siguiente aplicación:

 public class DeadlockProgram { public static void main(String[] args) throws Exception { Object resourceA = new Object(); Object resourceB = new Object(); Thread threadLockingResourceAFirst = new Thread(new DeadlockRunnable(resourceA, resourceB)); Thread threadLockingResourceBFirst = new Thread(new DeadlockRunnable(resourceA, resourceB)); threadLockingResourceAFirst.start(); Thread.sleep(500); threadLockingResourceBFirst.start(); } private static class DeadlockRunnable implements Runnable { private final Object firstResource; private final Object secondResource; public DeadlockRunnable(Object firstResource, Object secondResource) { this.firstResource = firstResource; this.secondResource = secondResource; } @Override public void run() { try { synchronized (firstResource) { printLockedResource(firstResource); Thread.sleep(1000); synchronized (secondResource) { printLockedResource(secondResource); } } } catch (InterruptedException e) { System.out.println("Exception occurred: " + e); } } private static void printLockedResource(Object resource) { System.out.println(Thread.currentThread().getName() + ": locked resource -> " + resource); } } } 

Esta aplicación finalizará sin enclavamiento, y como resultado obtendremos el siguiente resultado (tenga en cuenta que las direcciones de la clase Object han cambiado):

 Hilo-0: recurso bloqueado -> java.lang.Object@1ad895d1
 Hilo-0: recurso bloqueado -> java.lang.Object@6e41d7dd
 Hilo-1: recurso bloqueado -> java.lang.Object@1ad895d1
 Hilo-1: recurso bloqueado -> java.lang.Object@6e41d7dd

, , thread dump, . ( deadlock-). , .

Thread Dump-


.

JVM . ( , ).

.

- — Thread Dump Analyzers (TDAs). Java thread dump- - , . , . , .

TDA:


. .

Conclusión


Thread dump- — Java-, . , .

deadlock, . . , — .

, Java- thread dump-. , .

, thread dump — « » , , Java-.

Java . , deadlock- .

, .

Source: https://habr.com/ru/post/es427513/


All Articles