Como analisar um despejo de thread java?

100

Estou tentando entender mais sobre java, especialmente sobre gerenciamento de memória e threads. Por esse motivo, recentemente descobri interesse em examinar despejos de thread.

Aqui estão algumas linhas tiradas de um aplicativo da web usando VisualVM, uma ferramenta integrada 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)

Primeiro, tenho dúvidas sobre alguns nomes de variáveis:

  • o que significa tid e nid?
  • Qual é a figura entre parênteses ao quadrado após Object.wait?

Então, para o rastreamento de pilha em si:

  • o que significa aguardar <.....> (um java.lang ....) e qual é o número em <..>
  • o que significa bloqueado <.....> (um java.lang ....) mesma pergunta, o que está em <..>

Achei que a palavra bloqueado estivesse relacionada de alguma forma a uma condição de espera, no entanto, estava errado. Na verdade, estou me perguntando por que o bloqueio é repetido três vezes, mas o encadeamento está em estado executável, conforme visto no mesmo dump:

"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)

Então, por último, este foi o pior deles:

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

Este segmento está em estado executável, mas está aguardando uma condição. Qual é a condição e o que é 0x00000?

Por que o rastreamento de pilha é tão curto sem nenhuma evidência da classe de encadeamento?

Se você pudesse responder a todas as minhas perguntas, eu ficaria muito grato.

obrigado

Leonardo
fonte

Respostas:

113

O TID é o id do cabeçote e o NID é: ID do thread nativo. Este ID é altamente dependente da plataforma. É o NID em despejos de thread jstack. No Windows, é simplesmente a ID de thread no nível do sistema operacional em um processo. No Linux e Solaris, é o PID da thread (que por sua vez é um processo leve). No Mac OS X, é considerado o valor nativo pthread_t.

Vá para este link: ID do encadeamento em nível de Java : para obter uma definição e uma explicação adicional desses dois termos.

No site da IBM encontrei este link: Como interpretar um despejo de thread . que cobre isso em mais detalhes:

Ele explica o que significa esperar: Um bloqueio impede que mais de uma entidade acesse um recurso compartilhado. Cada objeto em Java ™ possui um bloqueio associado (obtido usando um bloco ou método sincronizado). No caso da JVM, os encadeamentos competem por vários recursos na JVM e bloqueiam em objetos Java.

Em seguida, descreve o monitor como um tipo especial de mecanismo de bloqueio usado na JVM para permitir a sincronização flexível entre os encadeamentos. Para os fins desta seção, leia os termos monitorar e bloquear de forma intercambiável.

Então vai mais longe:

Para evitar ter um monitor em cada objeto, a JVM geralmente usa um sinalizador em uma classe ou bloco de método para indicar que o item está bloqueado. Na maioria das vezes, um trecho de código transitará por alguma seção bloqueada sem contenção. Portanto, a bandeira guardiã é suficiente para proteger esse trecho de código. Isso é chamado de monitor plano. No entanto, se outro encadeamento deseja acessar algum código que está bloqueado, ocorreu uma contenção genuína. A JVM agora deve criar (ou aumentar) o objeto de monitor para conter o segundo encadeamento e providenciar um mecanismo de sinalização para coordenar o acesso à seção de código. Este monitor agora é chamado de monitor inflado.

Aqui está uma explicação mais detalhada do que você está vendo nas linhas do despejo de thread. Um encadeamento Java é implementado por um encadeamento nativo do sistema operacional. Cada tópico é representado por uma linha em negrito, como:

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

* Os 6 itens a seguir explicam isso, pois eu os comparei no exemplo, os valores entre colchetes []:

  1. nome [ Thread-1 ],
  2. identificador [ 0x9017A0 ],
  3. Endereço da estrutura de dados JVM [ 0x23EAC8 ],
  4. estado atual [ R ],
  5. identificador de thread nativo [ 0x6E4 ],
  6. e prioridade [ 5 ].

A "espera" parece ser um encadeamento daemon associado ao próprio jvm e não o encadeamento do aplicativo em si. Quando você obtém um "in Object.wait ()", isso significa que o thread daemon, "finalizer" aqui, está aguardando uma notificação sobre um bloqueio em um objeto, neste caso, ele mostra qual notificação está aguardando: "- aguardando <0x27ef0288> (um java.lang.ref.ReferenceQueue $ Lock) "

A definição de ReferenceQueue é: Filas de referência, às quais os objetos de referência registrados são anexados pelo coletor de lixo depois que as alterações de alcançabilidade apropriadas são detectadas.

O encadeamento finalizador é executado para que a coleta de lixo opere para limpar os recursos associados a um objeto. Se estou vendo corretamente, o finalizador não pode obter o bloqueio para este objeto: java.lang.ref.ReferenceQueue.remove (ReferenceQueue.java:118) porque o objeto java está executando um método, então o thread do finalizador está bloqueado até que o objeto termine com sua tarefa atual.

Além disso, o finalizador não está apenas tentando recuperar memória, está mais envolvido do que isso na limpeza de recursos. Eu preciso estudar mais sobre isso, mas se você tiver arquivos abertos, sockets, etc ... relacionados a métodos de objetos, então o finalizador irá trabalhar para liberar esses itens também.

Qual é a figura entre parênteses ao quadrado após Object.wait no despejo de thread?

É um ponteiro na memória para o segmento. Aqui está uma descrição mais detalhada:

C.4.1 Informações do Thread

A primeira parte da seção do encadeamento mostra o encadeamento que provocou o erro fatal, da seguinte maneira:

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

O ponteiro de thread é o ponteiro para a estrutura de thread interna do Java VM. Geralmente não é de interesse, a menos que você esteja depurando uma VM Java ativa ou um arquivo principal.

Esta última descrição veio de: Guia de solução de problemas para Java SE 6 com HotSpot VM

Aqui estão mais alguns links sobre despejos de thread:

James Drinkard
fonte
11

Além da excelente resposta de @James Drinkard:

Observe que, dependendo da implementação subjacente, o java.lang.Thread.State de um thread que está bloqueado em um método nativo pode ser relatado como RUNNABLE, ondeA 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.

Acontece que essa descrição também abrange o bloqueio em uma chamada de sistema operacional, como uma pesquisa ou operação de leitura - presumivelmente porque não há garantia de que a JVM possa saber quando uma chamada de método nativo foi bloqueada no nível do sistema operacional.

Muitas discussões sobre despejos de encadeamentos JVM que eu vi ignoram esta possibilidade completamente, ou alegremente a examinam sem considerar as implicações - uma das quais é que as ferramentas de monitoramento podem relatar confusamente que vários encadeamentos estão "em execução" e, além disso, que todos eles estão funcionando a 100%.

Jeremy
fonte