Dicas, Java, JVM, Tools

Colhendo envidências em caso de erro (crash) na JVM

Sabe aquela situação em que a equipe responsável pelo ambiente de produção chega pra você e diz: “o servidor X está caindo toda hora… já olhamos os recursos da máquina (rede, memória, cpu e disco), inclusive o log do servidor, mas não identificamos nada…”

Isso mesmo! Há casos em que seu processo caiu sem dar explicações e sem deixar pista alguma.

Já tive a oportunidade de passar por essa situação algumas vezes… Trata-se de uma situação complicada, pois quase sempre ocorre em um momento e ambiente crítico onde o tempo para solução é o seu pior inimigo. Bem, após bater cabeça com esse tipo de problema, posso afirmar que a melhor opção é preparar o seu ambiente para colher algumas evidências quando o problema voltar a ocorrer. Como quase tudo na TI esse tipo de situação é inevitável. Softwares e Hardware podem falhar por “n” motivos. Nesse post deixo algumas dicas que podem lhe ajudar na investigação “pós queda” de um processo Java (rodando em cima de uma JVM).

Existem dois casos em que é bastante comum o processo da JVM cair:

  1. insuficiência de memória
  2. algum erro fatal da jvm

O tipo de erro relacionado à insuficiência de memória é o conhecido por OutOfMemoryError (OOM) e ocorre devido à algum memory leak no código da aplicação. Para analisar a causa desse tipo de erro é interessante ter em mãos o estado (snapshot) do Heap da JVM no momento em que o erro ocorre. Para isso é necessário habilitar alguns parâmetros na JVM conforme abaixo:

Geração do Dump (formato hprof) do Heap da JVM.

Adicionar as seguintes propriedades no comando que inicia a JVM. Caso o comando seja parametrizado em um arquivo de configuração, basta usar uma variável JAVA_OPTS como no exemplo abaixo.

# HeapDUMP e Core DUMP
# registra a data e hora de início do processo
DATE_START=`date  +%d-%m-%Y-%k%M`

# Habilita a geração do DUMP de memória (HPROF)
JAVA_OPTS="$JAVA_OPTS -XX:+HeapDumpOnOutOfMemoryError "
JAVA_OPTS="$JAVA_OPTS -XX:OnOutOfMemoryError=<execute algum comando shell aqui (ex: 'kill -9%p')> "
JAVA_OPTS="$JAVA_OPTS -XX:HeapDumpPath=/var/log/jvm/heapdump_$DATE_START.hprof "

 

NOTA:

  • Recentemente encontrei uma implementação alternativa à opção ‘-XX:OnOutOfMemoryError’ interessante. Vale a pena testar!
  • o arquivo de dump no formato .hprof pode ser aberto com os utilizatários jVisualvm (fornecido pelo JDK Sun/Oracle Hotspot) ou Eclipse Memory Analizer (MAT). É necessário que a versão e arquitetura do JDK utilizado na análise dump seja idêntica à JVM utilizada pelo servidor de aplicação onde o erro ocorreu.

O segundo caso de erro pode ser gerado por algum problema entre a JVM e SO. Um caso típico é o erro de estouro de pilha conhecido como StackOverflowError. Esse tipo de erro pode ocorrer caso a aplicação utilize algum tipo de loop infinito ou uma recursividade muito profunda. O tamanho da pilha alocada pela JVM durante a criação de uma thread java é de 1024KB (1mb) em um sistema Linux x64. É possível alterar esse valor para mais ou para menos a depender da necessidade da aplicação. O parâmetro da JVM -Xss:<n>k altera o valor da pilha, sendo que <n> é um valor inteiro.

Geração do coredump (threads e memória) da JVM

Adicionar as seguintes propriedades no comando que inicia a JVM.

# Habilita a geração de coredump (BIN)
JAVA_OPTS="$JAVA_OPTS -XX:OnError='gcore -o /var/log/jvm/jboss_PID%p_$DATE_START.coredump %p' "
JAVA_OPTS="$JAVA_OPTS -XX:ErrorFile=/var/log/jvm/hs_err_jboss_PID%p_$DATE_START.log "

NOTAS:

  • A opção ‘-XX:OnError‘ é executada apenas quando  a causa raiz do erro for Nativo devido a alguma falha fora da JVM – JVM Native Crash.
  • o arquivo de dump no formato hprof pode ser aberto com os utilizatários jVisualvm (fornecido pelo JDK) ou Eclipse Memory Analizer (MAT).  É necessário que a versão e arquitetura do JDK utilizado na análise dump seja idêntica à JVM utilizada pelo servidor de aplicação onde o erro ocorreu.
  • Para que o comando gcore (invocado pela JVM após o evento de erro) funcione é necessário que o pacote gdb (A GNU source-level debugger for C, C++, Fortran and other languages) esteja devidamente instalado no Sistema Operacional.

Uma opção mais eficiente para geração de Dumps em JVMs com Heaps grandes (>= 2gb)

Utilizar o jmap em heap muito grandes pode demorar horas para concluir. Em algumas situações é necessário reiniciar o serviço imediatamente após a falha para diminuir o tempo em que ele fica fora do ar. Uma excelente alternativa é descrita nesse artigo disponível no Blog da Atlassian: So you want your JVM’s heap…

Gerando dumps da JVM em tempo de runtime manualmente

O formato binário pode ser aberto com as ferramentas: jstack (JDK), jVisualVM (JDK), Eclipse MAT ou qualquer outro profiler Java que reconheça o formato HPROF.

jmap -dump:live,format=b,file=jvm_heap.bin <PID>

O formato texto pode ser aberto com as ferramentas: jVisualVM (JDK), IBM Thread Analizer ou qualquer outro profiler Java que reconheça dump de threads Java.

jstack -F -l <PID> > /tmp/jvm_thread.dump

NOTA: os procedimentos acima causam o travamento das threads (equivalente ao efeito Stop The World do FullGC). Portanto utilize com cautela em ambiente de produção!

A análise dos dumps citados neste post é assunto para um novo post. Existem várias ferramentas que podem ser utilizadas na análise. Além das ferramentas é importante ter conhecimento da arquitertura da Máquina Virtual Java utilizada. No caso do dump de memória (heap) é importante conhecer a divisão geracional dos pools de memória da JVM , bem como os vários mecanismos de coleta de lixo. Para o dump de threads é importante conhecer um pouco sobre a execução de threads na plataforma Java: sincronização, pilha de execução, thread monitor, thread status (WAINTING, RUNNING, BLOCKED, etc). Com a ferramenta certa e com um pouco de paciência é possível chegar a tão desejada causa raiz do problema: nome da classe, nome do método, nome da biblioteca ou até mesmo a linha de código que causa ou influência o crash do processo Java.

Java, Segurança Digital, Tools

Manutenção em Java Key Stores (JKS) usando o Keytool

Manutenção de Kestores já existentes

Em certas situações é necessário realizar uma manutenção em um keystore já existente no servidor. A manutenção pode ser motivada pela necessidade de importar/exportar uma chave/certitificado de outro ambiente no keystore em questão.

Segue abaixo alguns comandos úteis da ferramenta keytool (fornecida pelo JDK da Sun/Oracle)

Listar o conteúdo de um keystore

Antes de qualquer manutenção no keystore é interessante listar o seu conteúdo.

  • O comando abaixo lista todos os certificados armazenados no keystore (JKS) informado.
keytool -list -keystore server_keystore.jks

A saída do comando deve ser semelhante a apresentada pela listagem abaixo:

 *****************  WARNING WARNING WARNING  *****************                                                                                                                   
 * The integrity of the information stored in your keystore  *
 * has NOT been verified!  In order to verify its integrity, *
 * you must provide your keystore password.                  *
 *****************  WARNING WARNING WARNING  *****************

 Keystore type: JKS
 Keystore provider: SUN

 Your keystore contains 4 entries

 server1.domain.com, Mar 26, 2012, PrivateKeyEntry, 
 Certificate fingerprint (MD5): A7:8B:B7:9A:11:50:CC:98:4C:B5:89:BA:04:3B:CD:AD
 server2.domain.com, Mar 26, 2012, PrivateKeyEntry, 
 Certificate fingerprint (MD5): 2F:A2:50:65:C9:B2:2B:16:C0:2E:10:8A:8C:AB:50:E0
 server3.domain.com, Mar 26, 2012, PrivateKeyEntry, 
 Certificate fingerprint (MD5): 98:CC:AF:88:82:85:07:9A:23:FA:3C:FB:AD:1C:14:62
 server4.domain.com, Mar 26, 2012, PrivateKeyEntry, 
 Certificate fingerprint (MD5): 84:57:8C:40:03:12:F1:56:0B:89:9D:1E:20:E6:48:B5

Exportar um certificado específico

  • No exemplo abaixo o certificado (identificado pelo alias server3.domain.com) armazenado no keystore server_keystore.jks será exportado para o arquivo server3.cer
keytool -export -alias server3.domain.com -keystore server_keystore.jks -file server3.cer

Importar um certificado específico

De posse do certificado exportado no passo anterior basta usar o comando keytool –import para importá-lo em um keystore (JKS) diferente.

  • No exemplo abaixo o certificado (identificado pelo alias server1.domain.com) armazenado no arquivo server1.cer será importado dentro do keystore client_keystore.jks
keytool -import -alias server1.domain.com -file server1.cer -keystore client_keystore.jks

Clonar um certificado específico

Caso seja necessário clonar uma determinada chave/certificado armazenado no keystore basta utilizar o comando keytool -keyclone.

  • No exemplo abaixo o certificado identificado pelo alias server3.domain.com será clonado com um novo alias (clone_server3.domain.com) dentro do keystore server_keystore.jks
keytool -keyclone -alias server3.domain.com -dest clone_server3 -keystore server_keystore.jks

obs:
1) você será questionado quanto a senha que deseja definir para a chave clonada. Caso queira manter a senha da chave original (clonada) confirme com ‘<Enter>’.
2) a opção ‘-keyclone’ funciona apenas para certificados contendo o par de chaves pública/privada. Não é possível clonar (duplciar) chaves públicas.

Excluir um certificado específico

Caso seja necessário excluir uma determinada chave/certificado armazenado no keystore basta utilizar o comando keytool -delete.

  • No exemplo abaixo o certificado identificado pelo alias server1.domain.com será excluído do keystore server_keystore.jks
keytool -delete -alias server1.domain.com -keystore server_keystore.jks

Renomear (altear o alias) um certificado específico

Caso seja necessário renomear (alterar o alias) uma determinada chave/certificado armazenado no keystore basta utilizar o comando keytool -changealias.

  • No exemplo abaixo o certificado identificado pelo alias server1.domain.com será renomeado para o alias serverX no keystore server_keystore.jks
keytool -changealias -alias server1.domain.com -destalias serverX.domain.com -keystore server_keystore.jks
  • para mais detalhes sobre a ferramenta keytool consulte a documentação oficial do JDK disponível em [1]