JAR: Could not find main class

     Oggi voglio scrivere di un problema in cui può essersi imbattuto chiunque abbia provato a creare uno script batch a partire da un progetto java:

    "Could not find main class ...."

Controllate e ricontrollate il file MANIFEST e il package e il nome della classe sono corretti...cosa diamine può essere?
La cosa più stupida del mondo...il file MANIFEST deve terminare con una riga completamente vuota. Ebbene si, è così. Al proposito c'è una nota importante nella documentazione ufficiale sulla sintassi del file MANIFEST.

Creare uno script batch in ambiente Windows
A questo punto, visto che ci siamo, vediamo come possiamo lanciare il nostro main da uno script batch nella maniera più pulita possibile.
Nella cartella principale dei sorgenti, sotto la dir "META-INF" andiamo ad editare il file "MANIFEST.MF":
   Manifest-Version: 1.0
   Main-Class: it.test.Test
   Class-Path: lib/ojdbc14.jar lib/log4j-1.2.16.jar
Oltre ad indicare il nome completo della classe main, andiamo a specificare con il class-path tutte le librerie necessarie al nostro progetto. Rispetto alla posizione che occuperà il jar prodotto per il nostro progetto le librerie si troveranno nella cartella "lib".
A questo punto esportiamo il nostro progetto come file jar (ad esempio tramite eclipse) avendo l'accortenza di specificare l'uso di un file MANIFEST esistente, quello che abbiamo appena editato.

Dopodichè creaiamo una cartella per contenere tutto quanto necessario al nostro batch, con l'alberatura mostrata in figura:



All'interno della cartella "bin" andremo a disporre il file jar creato e a creare una sotto-cartella "lib" dove disporremo tutte le librerie indicate nel class-path.

A questo punto editiamo lo script batch vero e proprio, test.bat, come di seguito:
   set currentdate=%DATE:~-4%_%DATE:~3,2%_%DATE:~0,2%
   set currenttime=%TIME:~0,2%_%TIME:~3,2%_%TIME:~6,2%_%TIME:~9,2%
   set timestamp=%currentdate%H%currenttime%
   java -Dlog4j.configuration=file:log4j.xml -jar bin/Test.jar testParameter > log/test-%timestamp%.log
Lo script specifica il file di configirazione per il log (usiamo log4j), il nostro jar da richiamare, "Test.jar", un parametro per il main il cui valore è "testParameter" ed infine redirige l'output della console verso un file di log nell'omonima cartella.

Java: parsing delle date con controllo di validità

    A chi non è mai capitato di dover parsare una stringa per convertirla in un oggetto Date?

La cosa è abbastanza banale in Java mediante l'oggetto java.text.SimpleDateFormat, ma va ricordato che il comportamento di default del metodo parse è "indulgente" (Lenient), ovvero effettua il controllo sintattico della stringa ma non semantico.
Cosa vuol dire in pratica? Ad es. la stringa "30/02/2010" è considerata una data valida anche se il 30 febbraio non esiste sul calendario, nessuna eccezione verrà sollevata e la stringa verrà convertita come java.util.Date in 02/03/2010.

Se tale comportamento non è quello che intendiamo ottenere dobbiamo ricordarci di settare a false l'apposita proprietà, come nell'esempio seguente, dove controlliamo che una stringa generica sia una data valida:

public static boolean isDate(String string) {
  try {
    SimpleDateFormat sf = new SimpleDateFormat("dd/MM/yyyy",Locale.ITALIAN);
    sf.setLenient(false);
    sf.parse(string);
    return true;
  } catch (Exception e) {
    return false;
  }
}

Java: caricare una risorsa interna in una applicazione esportata come JAR

     In questi giorni mi sono imbattuto nel seguente problema:
ho realizzato una applicazione che fa uso di un file di properties, l'ho testata, tutto ok.
Esportata come jar, però, la mia applicazione ha smesso di funzionare perché il file properties risultava non trovato (FileNotFoundException). Strano no? 
Il problema era evidentemente nel percorso del file...ma come fare a specificare un percorso relativo al contenuto del pacchetto jar?

Ho scoperto, con mia sorpresa, che ottenendo da un oggetto la sua classe di runtime è possibile invocare un metodo denominato getResourceAsStream(String name) che permette di ricercare una risorsa delegando tale compito al Class Loader che ha caricato la specifica classe. In questo modo la risorsa viene cercato nel contesto dell'applicazione, quindi nel mio caso all'interno del jar, ottenendo il risultato che cercavo. Ma vediamo il codice corrispondente, chiarirà ogni dubbio:

...
Properties p = new Properties();
p.load(this.getClass().getResourceAsStream("/myApp.properties"));
...
    
il file "myApp.properties" verrà ricercato nella root dell'archivio jar.
Importate è sottolineare la presenza del carattere "/" che precede il nome file per riferire come percorso la root. In assenza di questo, infatti, il file viene ricercato all'interno del package che contiene la classe riferita con this nel codice di esempio. 

JVM: Gestione heap

    Per chi sviluppa in Java prima o poi arriva il momento di confrontarsi con problemi di tuning della JVM, in particolare dei parametri inerenti l’utilizzo della memoria dinamica (heap).
È quello che è capitato a me un po’ di tempo fa, dopo aver capito che la causa che portava a chash continui dell’applicazione su cui stavo lavorando era la frequenza altissima di Full GC.
La configurazione della memoria può incidere notevolmente sulle prestazioni delle nostre applicazioni. È quindi fondamentale capire come la JVM gestisca tale porzione della memoria ad essa riservata prima di agire sui parametri di configurazione.
Ok, cominciamo.

     Ogni applicazione java è running nel contesto di una JVM, o meglio di un’istanza della JVM (un processo java).  Una parte della memoria riservata a tale istanza è adibita a memoria dinamica (heap – letteralmente “mucchio”).
Lo spazio occupato da tale area non è fisso, quindi, ma varia durante l’esecuzione dell’applicazione: è questa l’area dove finiscono tutti i nostri oggetti java durante il loro ciclo di vita.
Mentre l’allocazione degli oggetti è a carico degli sviluppatori (con la new), in Java, la deallocazione è gestita automaticamente dalla JVM tramite il Garbage Collector (GC), un thread che passa in rassegna la heap deallocando tutti gli oggetti che non sono più referenziati e che quindi hanno completato il loro ciclo di vita.
Un lavoro in meno per noi…bello no?
Il “guaio” è che quando la JVM decide di far entrare in gioco il GC è necessario stoppare l’esecuzione di tutti gli altri thread fino a che il GC non ha finito. Quindi in questo lasso di tempo la nostra applicazione è running ma non lavora. Dunque è importante che il GC venga chiamato il minor numero di volte possibile e che il suo lavoro abbia durata minore possibile per non degradare anche pesantemente le prestazioni delle nostre applicazioni.
Se il GC dovesse ogni volta passare in rassegna tutti gli oggetti correntemente in vita, per applicazioni medio-grandi il suo utilizzo sarebbe proibitivo. È per questo motivo che in realtà esistono più GC ciascuno assegnato ad una generazione diversa di oggetti.

Generazioni
Osservando il comportamento di  molte applicazioni si è riscontrato che la maggior parte degli oggetti che vengono allocati hanno vita breve: quella di un ciclo for, ad esempio, o di un singolo metodo. Per questo motivo all’interno della heap distinguiamo tre generazioni di oggetti:  young, tenured o old, permanent. Esiste un GC per ognuna di essa che entra in gioco ogni qualvolta le generazione si satura.
La saturazione della young genaration comporta la cosiddetta minor collection che è piuttosto veloce ed efficiente, mentre se viene saturata la tenured generation si parla di major collecction o Full GC che può essere molto più lenta perché riguarda tutti gli oggetti in vita.
La young e la tenured generation sono usate per gestire i nostri oggetti java mentre la permanent è usata per gestire metadati necessari alla JVM, come ad esempio la descrizione delle nostre classi, metodi e tutto quanto accessibile tramite reflaction. Perché usare memoria dinamica per tali informazioni? Perché in Java il caricamento delle classi è dinamico, tramite ClassLoader (magari su questo argomento farò un post dedicato).

A questo punto vediamo come è strutturata la hep:

Fig.1 - Struttura heap
  1. Inizialmente viene riservata una certa quantità di memoria alla heap (cosiddetta committed memory) anche se non usata. In ogni istante una delle due aree indicate come survivor space è sempre vuota.
  2. Quando a runtime la nostra applicazione crea un nuovo oggetto esso viene allocato nella young generation nell'area denominata eden.
  3. Quando l'eden si riempe viene invocata la minor collection che dealloca gli oggetti in eden e nella survivor space non vuota che non sono più referenziati. Gli oggetti "superstiti" vengono spostati nella survivor space vuota.
  4. Se non c'è abbastanza spazio per tutti nella survivor space vuota quelli più "vecchi" (quelli che sono stati spostati più volte all'interno della young generation) li si promuove nella tenured generation.
  5. Se la tenured generation diventa piena e/o non è in grado di accogliere tutti gli oggetti superstiti si rende necessaria la major collection o Full GC.
  6. La JVM aumenta o riduce la dimensione delle generazioni in base a dei parametri configurabili (min e max spazio da destinare alla heap, % heap libera, ecc).
Considerazioni sul dimensionamento delle generazioni
Osserviamo che nel caso peggiore della minor collection tutti gli oggetti possono risultare ancora in vita, e quindi la survivor space libera può non bastare per lo spostamento. Pertanto affinché la tenured generation possa ospitarli tutti dovrà avere dimensioni almeno pari ad eden + survivor space piena. 
Inoltre se la tenured generation è piccola ciò implica una maggiore frequenza della Full GC che, come detto, è costosa in termini di prestazioni.
Nel caso di survivor space troppo piccola si è costretti ad usare spesso la tenured, viceversa se troppo grande avremo inutile spreco di memoria.

Tipologie di GC
Esistono diverse tipologie di GC che la JVM può usare. Per la minor e per la major collection possono essere usati GC di tipologie diverse:
  • Serial GC, utilizza un solo thread per realizzare il comportamento descritto. E' applicabile sia alla minor che alla major collection;
  • Thrughput GC, è applicabile alla minor collection ed utilizza più thread concorrenti per scandire la memoria. Di default il loro numero è pari al numero di processori del sistema host. Per avere vantaggi di prestazione rispetto al seriale è necessario avere più di due processori perché il prezzo della concorrenza dei thread è un overhead dovuto alla sincronizzazione tra questi. Inoltre, siccome ogni thread utilizza una diversa area della tenured generation in cui spostare gli oggetti "vecchi", può aver luongo una frammentazione di tale generazione con conseguente spreco di memoria.
  • Parallel Compaction GCè applicabile alla tenured generation ed utilizza più thread concorrenti per scandire la memoria. Valgono le stesse considerazioni fatte Thrughput GC.
  • Concurrent Low Pause, è applicabile alla tenured generation e comporta un minimo fermo dei thread applicativi durante il suo lavoro. Quindi il Full GC è praticamente concorrente con l'applicazione. In ogni caso essendo un thread ulteriore che si aggiunge all'applicazione, per non avere un degrado delle performance è consigliabile avere più di 2 processori.


Nel prossimo post vedremo come possiamo sfruttare queste conoscenze per ottenere maggiori performance dalle nostre applicazioni e quali sono i parametri per un tuning della nostra JVM.

Il primo post

Eccolo! Vi presento il primo post di Javando.

Ho aperto questo blog perché mi sono accorto che spesso, durante lo sviluppo di software in Java (oramai quasi 4 anni!), mi capita di affrontare dei problemi, studiarli e scrivere degli appunti su fogli vari che, prima o poi (e stranamente quando possono tornarmi utili) non trovo più (sarà che sono sbadato?).

Senza pretese, quindi, per me e per chi può trovare interessante quanto scrivo, mi propongo di pubblicare in questo spazio le mie piccole "scoperte" giornaliere sul mondo Java...quindi Javando!