Ho deciso di mettere nero su bianco (o dovrei dire “bit su disco”) alcuni appunti relativi alla generazione di un file di dump per andare ad analizzare lo stato della nostra JVM.
Sin dalle sue prime versioni, la JDK viene rilasciata con alcuni tool a corredo che possono semplificare alcuni compiti e ci vengono incontro in caso di necessita.
Questi tool li troviamo all’interno della cartella JAVA_HOME/bin. I più semplici e visuali sono jconsole e jvisualvm ma ne troviamo tanti altri come ad esempio jps, jmap o jhat.
Questa è la pagina di riferimento ufficiale dal sito di Oracle: http://docs.oracle.com/javase/7/docs/webnotes/tsg/TSG-VM/html/tooldescr.html
Se date una rapida lettura vi farete un’idea di tutti i tool e poi ovviamente ogni tool ha una sua specifica pagina di riferimento.

Veniamo quindi al nostro problema: vogliamo controllare lo stato di esecuzione della nostra JVM con dentro un programma che riteniamo avere dei problemi.

Nel seguito assumo che il problema venga affrontato su sistemi Unix.

Passo1: identificare il pid del processo java in esecuzione

Volendo usare un comodo tool offerto dalla nostra jdk, possiamo utilizzare jps il cui output sarà simile al seguente:

9444 GreedySample
9458 Jps

In alternativa possiamo usare la sempre verde riga di comando unix:

ps aux | grep java

oppure

ps -ef | grep java

Il PID del processo in esecuzione è fondamentale per poter lanciare i tool successivi.

Passo2: generare un core dump con gdb (metodo da sistemista)

Dalla riga di comando, con utente root, lanciamo il comando gdb (GNU Debugger):

gdb program JAVA_PID

oppure

gdb –pid=JAVA_PID

A questo punto il vostro prompt cambierà e vi troverete all’interno del debugger.
Lanciamo quindi il comando

gcore [nome_file]

Se non impostiamo il nome file allora andremo a generare un file dal nome core.JAVA_PID dentro la working directory.
Una volta completato questo task, lanciate il comando

detach

e quindi

quit

per uscire dal gdb.

State attenti a due cose se usate gdb:

  1. l’applicazione java si blocca e viene rilasciata solo dopo che lanciate la “detach”. Nel caso di applicazioni in esecuzione su application server potrebbe essere necessario riavviare l’intera applicazione.
  2. Il core dump che generiamo è un file molto grosso (oltre 1 giga).

Il core dump generato non può ancora essere letto con i normali tool offerti da java, occorre infatti trasformarlo in un formato binario con estensione “hprof”.
Lanciate il tool java jmap:

./jmap -dump:format=b,file=dump.hprof /JAVA_HOME/jdk/bin/java core.JAVA_PID

Nello specifico:

    • “-dump:format=b,file=dump.hprof” è un unico attibuto che specifica il nome del file che vogliamo generare impostandone il formato binario.

 

  • “/JAVA_HOME/jdk/bin/java” è il path completo del comendo java relativo alla JVM per la quale abbiamo generato il core dump. Se non siete sicuri di quale sia il “java” che era in esecuzione sul processo di vostro interesse, potete lanciare “gdb –core=core.JAVA_PID” e leggere all’interno dell’output ottenuto l’informazione che vi interessa.
  • “core.JAVA_PID” è il nome del core dump

Passo2 (alternativo): generare un core dump con jmap (metodo da programmatore)

Se quanto descritto al passo precedente vi sembra complicato, potete anche utilizzare esclusivamente il tool jmap, in questo modo:

./jmap -F -dump:format=b,file=dump.hprof JAVA_PID

Passo3: visualizzare il dump.hprof

Usiamo a questo punto il tool jhat:

./jhat -J-mx1024m dump.hprof

Questo tool, esegue in locale un server sulla porta 7000 e abbiamo la possibilità di esplorare il core dump con il nostro browser preferito.

Ho notato però che jhat ha dei limiti legati alla dimensione del core da aprire. Nonostante la possibilità di impostare la memoria all’avvio, come vediamo con l’opzione -J, il tool va facilmente in OutOfMemory. Se dovete quindi aprire grossi file scaricate Eclipse Memory Analyzer, un tool stand-alone costruito sul framework di Eclipse:

http://www.eclipse.org/mat/

Altre info utili

Ecco infine due utili opzioni legate a jmap.

La prima che vi propongo genera una statistica ordinata delle classi in esecuzione con relativo conteggio di istanze e memoria occupata:

./jmap -F -histo PID

Questo è un esempio di output semplificato

Size Count Class description
——————————————————-
39936 416 java.lang.Class
36384 758 java.nio.HeapCharBuffer
18704 167 java.util.GregorianCalendar
16032 167 sun.util.calendar.Gregorian$Date

 

2672 167 it.lcianci.test.performance.GreedySample
1536 48 java.util.concurrent.ConcurrentHashMap$Segment
1440 60 java.util.Hashtable$Entry

Il secondo comando vi permette invece di controllare lo stato dello Heap di java suddiviso nelle sue varie parti (di questo magari parleremo un’altra volta):

./jmap -F -heap PID

Classe di test
Nel mio caso specifico, ho realizzato una stupidissima classe che fa un pò di cose non troppo entusiasmanti, giusto per poter fare dei test:

package it.lcianci.test.performance;

import java.util.ArrayList;
import java.util.Calendar;

public class GreedySample {

 StringBuffer sb;
 
 public GreedySample() {
  Calendar c       = Calendar.getInstance();
  Long l           = new Long(c.getTimeInMillis());
  String s         = l.toString();
  StringBuffer sb1 = new StringBuffer(s);
  this.sb          = sb1;  
 }
 
 public String print() {
  return sb.toString();
 }
  
 public static void main(String[] args) {
  ArrayList<GreedySample> al = new ArrayList();
  
  while(true) {
   GreedySample greedy = new GreedySample();
   al.add(greedy);
   System.out.println(greedy.print());
   
   try {
    Thread.sleep(100);
   } catch (InterruptedException e) { e.printStackTrace(); }
  }
 }
}

Buon tuning a tutti.