Una volta completato lo sviluppo della nostra applicazione Java Enterprise ed effettuatone il deploy su Glassfish, nasce il problema di configurare l’application server per sfruttare al meglio le caratteristiche hardware del server in termini di performance.
Le configurazioni che Glassfish mette a disposizione sono moltissime, si va dai tradizionali parametri della JVM come la dimensione della heap o dello stack assegnato a ciascun thread, fino ad arrivare alle politiche di caching e alla I/O strategy di Grizzly, ovvero il framework che viene usato all’interno di Glassfish per servire le richieste HTTP/S sfruttando le API Java NIO.
Le configurazioni si dividono sostanzialmente in due parti:
- JVM Options
- Network config
Prima di iniziare a discutere di quali siano i valori ottimali per la propria macchina server è necessario acquisire alcune informazioni essenziali:
- architettura e versione JVM (32/64 bit e JDK version)
- RAM a disposizione
- numero di CPU del server
Valutare le risorse a nostra disposizione
Se state usando Linux queste informazioni sono molto facili da reperire. Per sapere che JVM abbiamo installata è possibile utilizzare il seguente comando:
[user@server ~]$ java -version java version "1.6.0_27" Java(TM) SE Runtime Environment (build 1.6.0_27-b07) Java HotSpot(TM) 64-Bit Server VM (build 20.2-b06, mixed mode)
Questo ci dice che disponiamo di una JVM 1.6.0.27 e soprattutto che quella installata è una versione a 64 bit (importante per il dimensionamento della heap). Se l’architettura lo supporta è sempre preferibile installare una versione a 64 bit.
Per quanto riguarda la RAM si può utilizzare il comando free, leggendo il primo valore della linea che inizia per “Mem.”.
</p> [user@server logs]$ free | grep Mem Mem: 16368988 16215132 153856 0 530768 9948688 <p style="text-align: justify;">
In questo caso la RAM disponibile è 16 GB.
Infine vediamo come sapere quante CPU ha il nostro server sfruttando il file /proc/cpuinfo.
[user@server ~]$ grep processor /proc/cpuinfo processor : 0 processor : 1 processor : 2 processor : 3
In questo caso il mio server dispone di 4 CPU fisiche.
Configurare le JVM Options
Le seguenti configurazioni si possono eseguire sia dall’interfaccia di amministrazione raggiungibile di default sulla porta 4848 della vostra istanza, oppure modificando il file domain.xml tramite un editor di testo come vim.
Vediamo sotto le opzioni principali, il loro valore ottimale rispetto ai parametri sopra, ed come queste variano il comportamento di Glassfish. Prima di iniziare è però necessario rimuovere alcune configurazioni di default, ovvero le opzioni:
- -Xmx 512m
- -client
Di seguito le opzioni che invece inseriremo nell’ambiente di produzione:
Opzione -server
Questa opzione serve a configurare ad alto livello il comportamento della JVM informandola che è in esecuzione su una macchina server, e consentendo una serie di ottimizzazione automatiche.
Opzione -XX:+AggressiveHeap
Questa opzione abilita una serie di ottimizzazioni destinata ad incrementare le performance su macchine dotate di un buon quantitativo di memoria fisica e di processori. Le configurazioni della JVM verranno personalizzate per i task intensivi e long-running come appunto un application server.
Opzione -Xmx
Questa opzione stabilisce un limite superiore per la dimensione della heap. Su sistemi server 64 bit, con JDK a 64 bit, e RAM >= 8 GB un setting che personalmente trovo adeguato è –Xmx4096m che di fatto destina un massimo di 4 GB a Glassfish.
Opzione -Xms
Questa opzione stabilisce la dimensione minima della heap ovvero quella che viene allocata alla creazione della JVM. Un setting troppo basso comporta ridimensionamenti frequenti ed inutili. Se siamo su una macchina server ed abbiamo settato il parametri Xmx a 4096 Mb non c’è alcuna ragione per scegliere un valore di Xms sotto i 2048 Mb, a meno che non ci serva avere RAM libera per servizi concorrenti. Proseguendo quindi con i valori raccomandati, per una macchina delle caratteristiche sopra, suggerisco di impostare -Xms2048m.
Opzione –Xss
Questa opzione serve a regolare lo stack size di ogni thread. Settarlo troppo piccolo può provocare effetti indesiderati durante l’operatività dei thread più complessi, configurare un valore troppo alto potrebbe impedire la creazione di un numero di thread sufficienti da parte del SO. Da segnalare che le versioni di JDK o successive impongono uno stack size minimo di 160k, mentre questo limite è più basso nelle versioni precedenti. In ogni caso un valore che ho empiricamente determinato essere valido per GF3 è -Xss256k.
Opzione -XX:+DisableExplicitGC
Questo parametro impedisce il verificarsi di chiamate a System.gc() a meno che non sia veramente necessario (normalmente esistono delle chiamate periodiche, che influiscono negativamente sulle performance dell’application server). Per applicazioni che fanno uso di cache, ma che hanno adeguate dimensioni di heap, disabilitare la chiamata periodica al Garbage Collector può avere risultati sorprendenti.
Opzione -XX:ParallelGCThreads=N
Questo parametro regola il numero di thread concorrenti che operano come Garbage Collector in un’istanza di JVM. Empiricamente questo valore N viene stabilito pari al numero di core disponibili se il numero di core è minore di 8. Se (buon per voi) disponete di un un server con più di 8 CPU potete o lasciare 8 come valore o sfruttare una famosa formula che circola in rete, ovvero:
Non ho mai avuto la fortuna di doverla utilizzare, per cui non so quanto aumentare di 5/8 per ogni CPU oltre le 8 influisca sulle performance.
Opzione -XX:+UseParallelOldGC
Questo parametro è utile, su macchine multicore, per rendere più efficiente le operazioni di full GC (esecuzione parallela della GC dello spazio di Old Generation). I guadagni in termini di prestazioni sono limitati alle operazioni di GC, e dipendono molto dal pattern di generation della web application. Se l’uso della Young Generation è intensivo (o se l’area dedicata è piccola) utilizzare questo parametro può dare incrementi di scalabilità interessanti. Su macchine multicore è un parametro da utilizzare in ogni caso.
Opzione -XX:+UseCompressedOops
Questo parametro serve, sulle JVM a 64 bit per ridurre l’occupazione di memoria dei generici object pointers che possono essere compressi a 32 bit per garantire la stessa efficienza nella gestione della cache dei registri CPU, senza compromettere le maggiori dimensioni della heap ottenibili appunto con JVM a 64 bit. Su macchine a 64 bit anche questo parametro va utilizzato in ogni caso.
Gestione dei thread pool
Un altro aspetto importante nel tuning di Glassfish è rappresentato dalla gestione dei thread pool, soprattutto quelli destinati a Grizzly che si occupa fisicamente di gestire le richieste HTTP. Nella versione 3 c’è stato un radicale cambio di strategia nella gestione delle richieste che ora operano in modo non bloccante secondo la non blocking I/O strategy. Questo consente di specificare pool più piccoli e di risparmiare in footprint sulla memoria ed in generale sul sistema. I valori dipendono molto dall’applicazione, consiglio di partire ma un setting minimo 40 / massimo 400 e di verificare le performanca all’aumento del carico innalzando il valore massimo se necessario. Questo per quanto riguarda il pool di servizio, per quello di amministrazione i valori possono essere lasciati su quelli di default.
Gestione della cache
Di default la cache dei file è disabilitata per tutti i connettori, il che fa si che le performance della maggior parte delle web application possano essere migliorate semplicemente abilitando questa opzione.
Come settings io uso un timeout di 120 secondi ed una cache di 40 Mb per connettore per complessivi 4K oggetti. Tutto dipende da cosa state sviluppando (es. un sito che serve per pubblicare gallerie fotografiche potrebbe avere settaggi molto più estremi di timeout e di dimensioni). Nel mio caso questi settings sono sufficienti, anche se ho sperimentato poco in questo campo.