Un`Architettura per il Monitoraggio on

Transcript

Un`Architettura per il Monitoraggio on
Facoltà di Ingegneria
Corso di Studi in Ingegneria Informatica
Tesi di laurea
Un’Architettura
per il Monitoraggio on-line
della Java Virtual Machine
Anno Accademico 2005/2006
relatore
Ch.mo prof. Domenico Cotroneo
correlatore
Ing. Salvatore Orlando
candidato
Rinaldi Ciro
matr. 534/926
Dedica
Alla mia Famiglia.
La scienza è fatta di dati come una casa di pietre. Ma un ammasso di dati
non è scienza più di quanto un mucchio di pietre sia una casa.
-Jules Henri Poincaré
Indice
1 La Java Virtual Machine, il Problema dell' Adabilità
1
1.1
Introduzione . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1
1.2
L'Architettura della Java Virtual Machine
. . . . . . . . . . .
3
1.2.1
Il ClassLoader . . . . . . . . . . . . . . . . . . . . . . .
3
1.2.2
La Gestione della Memoria Heap, la Garbage Collection
4
1.2.3
Principali Algoritmi di Garbage Collection . . . . . . .
6
1.2.4
La Java Virtual Machine ed il Compilatore HotSpot . .
7
1.2.5
Java Threads
. . . . . . . . . . . . . . . . . . . . . . .
9
1.2.6
Execution Engine . . . . . . . . . . . . . . . . . . . . .
9
1.2.7
Error Handling
. . . . . . . . . . . . . . . . . . . . . .
1.3
Classicazione dei Fallimenti della Java Virtual Machine
1.4
Conclusioni
11
. . .
12
. . . . . . . . . . . . . . . . . . . . . . . . . . . .
16
2 Il Problema del Monitoraggio nel Contesto della Java Virtual
Machine
2.1
17
Metodologie per lo Studio di Adabilità
. . . . . . . . . . . .
17
2.1.1
Measured-based Analysis . . . . . . . . . . . . . . . . .
19
2.1.2
Dependability Benchmarking
. . . . . . . . . . . . . .
19
2.1.3
Error Injection
. . . . . . . . . . . . . . . . . . . . . .
21
2.1.4
Robustness Testing . . . . . . . . . . . . . . . . . . . .
22
2.1.5
Aging Analysis
23
. . . . . . . . . . . . . . . . . . . . . .
iii
Un'Architettura per il monitoraggio online
della Java Virtual Machine
2.2
2.3
2.4
Tecniche e Tecnologie per il Monitoraggio . . . . . . . . . . . .
24
2.2.1
Java Virtual Machine Tool Interface . . . . . . . . . . .
24
2.2.2
Java Management eXtensions
. . . . . . . . . . . . . .
27
2.2.3
Bytecode Injection
. . . . . . . . . . . . . . . . . . . .
31
. . . . . . . . . . . . . . . . . . . . . .
34
2.3.1
ej-Technologies, JProler . . . . . . . . . . . . . . . . .
34
2.3.2
Sun Microsystems, HPROF: Heap Proler
. . . . . . .
35
. . . . . . . . . . . . . . . . . . . . . . . . . . . .
36
Tools di Monitoraggio
Conclusioni
3 JVMMon: Un Sistema di Monitoraggio Orientato alla Diagnosi
38
3.1
Introduzione a JVMMon . . . . . . . . . . . . . . . . . . . . .
38
3.2
Funzionalità Oerte . . . . . . . . . . . . . . . . . . . . . . . .
39
3.3
Architettura . . . . . . . . . . . . . . . . . . . . . . . . . . . .
40
3.3.1
JVMTI Agent . . . . . . . . . . . . . . . . . . . . . . .
42
3.3.2
Local Monitor Daemon . . . . . . . . . . . . . . . . . .
43
3.3.3
Data Collector
. . . . . . . . . . . . . . . . . . . . . .
43
. . . . . . . . . . . . . . . . . . . . . . . .
44
3.4.1
Descrizione dell' Agente JVMTI . . . . . . . . . . . . .
46
3.4.2
Instrumentation Tool, (Agente Java)
. . . . . . . . . .
49
3.4.3
Communication Layer
. . . . . . . . . . . . . . . . . .
53
Valutazione Sperimentale . . . . . . . . . . . . . . . . . . . . .
54
3.5.1
Obiettivo delle Misure
. . . . . . . . . . . . . . . . . .
54
3.5.2
Testbed Utilizzato
. . . . . . . . . . . . . . . . . . . .
54
3.5.3
Risultati . . . . . . . . . . . . . . . . . . . . . . . . . .
55
3.4
3.5
3.6
Soluzioni di Design
Conclusioni
. . . . . . . . . . . . . . . . . . . . . . . . . . . .
57
Appendice
60
A
60
iv
Elenco delle gure
1.1
Fasi del Class Loading
. . . . . . . . . . . . . . . . . . . . . .
3
1.2
Execution Engine . . . . . . . . . . . . . . . . . . . . . . . . .
10
1.3
Fault Classication . . . . . . . . . . . . . . . . . . . . . . . .
14
2.1
Analisi dei Fallimenti del Tandem System
. . . . . . . . . . .
18
2.2
Dependability Scenario . . . . . . . . . . . . . . . . . . . . . .
20
2.3
Virtual Machine . . . . . . . . . . . . . . . . . . . . . . . . . .
21
2.4
JMX Tiered Architecture . . . . . . . . . . . . . . . . . . . . .
29
2.5
Trasformazione di classi a Load Time . . . . . . . . . . . . . .
32
2.6
Esempi di funzionamento di JProler . . . . . . . . . . . . . .
35
3.1
Java Virtual Machine Heap
. . . . . . . . . . . . . . . . . . .
39
3.2
Architettura di JVMMon . . . . . . . . . . . . . . . . . . . . .
41
3.3
Evoluzione dello Stato della Virtual Machine . . . . . . . . . .
45
3.4
Object Hot Swapping . . . . . . . . . . . . . . . . . . . . . . .
52
3.5
Overhead introdotto da JVMmon . . . . . . . . . . . . . . . .
56
3.6
Performance Overhead, comparativa tra JVMMon ed HPROF
56
3.7
Memory Footprint Comparison
59
v
. . . . . . . . . . . . . . . . .
Elenco delle tabelle
3.1
Formato del Local Log File dell'agente JVMTI . . . . . . . . .
vi
42
Capitolo 1
La Java Virtual Machine, il
Problema dell' Adabilità
1.1 Introduzione
E' indubbio che la piattaforma Java, negli ultimi anni, è divenuta sempre
più utilizzata, anche in scenari profondamente diversi, grazie soprattutto, ad
alcune su caratteristiche peculiari quali: l'alto livello di portabilità, le astrazioni di programmazione ed in generale, grazie alle migliorie ed evoluzioni
della piattaforma stessa. Attualmente non è dicile riscontrare l'uso della
piattaforma nell'ambito di applicazioni cosidette critiche, come sistemi Real
Time, applicazioni transazionali, i sistemi di controllo remoto. Tuttavia la
JVM non possiede (almeno nelle attuali implementazioni) nessun supporto
diretto alla Fault Tolerance. Per poter sviluppare dei sistemi capaci di essere
tolleranti ai guasti, è necessario riuscire ad interpretare il comportamento
della con riferimento agli errori ed ai fallimenti che si manifestano. Questa
analisi, presentata successivamente, ci ha permesso di ssare dei focus, su
cui focalizzare l'attenzione, nello sviluppo di una unità di monitoraggio della
JVM; lo sviluppo di tale monitor, è da considerarsi come un primo passo
1
Un'Architettura per il monitoraggio online
della Java Virtual Machine
verso la progettazione di una JVM in grado di tollerare un buon numero di
situazioni anomale, di gestirle, e quindi di proseguire nelle sue attività senza
andare in crash, o comunque, in un qualsisi stato in cui l'adabilità dei risultati dell'elaborazione della stessa siano inattendibili oppure, addirittura,
completamente errati.
2
Un'Architettura per il monitoraggio online
della Java Virtual Machine
1.2 L'Architettura della Java Virtual Machine
1.2.1 Il ClassLoader
Figura 1.1: Fasi del Class Loading
Il sottosistema della Java Virtual Machine, incaricato di cercare e caricare
i tipi, è il Class Loader Subsystem.
due tipi di Class Loaders:
La Java Virtual Machine comprende
un Bootstrap Class Loader e gli User Dened
Class Loaders; Il primo, è parte dell'implementazione della JVM, gli User
Dened, invece, sono parte dell' applicazione Java in esecuzione. In realtà
va chiarito che, oltre a localizzare ed importare i dati binari per le classi, i
Class Loader sono anche responsabili di vericare la correttezza delle classi
importate, di allocare ed inizializzare la memoria per le variabili delle classi ed
inoltre partecipano a risolvere i riferimenti simbolici; in particolare è possibile
identicare queste tre fasi del processo di Class Loading 1.1:
1. Loading: vengono localizzati ed importati i dati binari di una classe.
2. Linking:
•
Verica: ci si assicura della correttezza del tipo importato;
3
Un'Architettura per il monitoraggio online
della Java Virtual Machine
•
Preparazione: si alloca memoria per le variabili della classe e la si
inizializza ai valori di default;
•
Risoluzione: si trasformano i riferimenti simbolici in riferimenti
diretti;
3. Initialization: viene invocato il codice Java che inizializza le variabili
della classe ai valori iniziali.
1.2.2 La Gestione della Memoria Heap, la Garbage Collection
Il processo della Garbage Collection comporta la liberazione delle risorse utilizzate da oggetti non più referenziati dall'applicazione in esecuzione.
La specica Java non impone, nella scelta delle tecniche e degli algoritmi di
Garbage Collection, alcun vincolo. Il Garbage Collector deve, in qualche modo, determinare quali oggetti non sono più referenziati da una applicazione
e rendere riutilizzabile lo spazio, della memoria heap, occupato dai suddetti
oggetti. Tra l'altro, compito del Garbage Collector è quello di combattere il
fenomeno della frammentazione dell'area heap. Esistono, chiaramente, una
serie di vantaggi indiscussi sfruttando un approccio gestito della memoria;
tra questi sicuramente possiamo annoverare l'incremento di produttività e la
salvaguardia automatica della memoria, evitando così crash accidentali derivanti da una erronea gestione delle risorse da liberare. In contrapposizione ai
suddetti vantaggi, vanno segnalati anche alcune problematiche che emergono
nella gestione automatica della memoria: bisogna tener conto dell'overhead
introdotto dal dover iterare costantemente l'albero dei riferimenti degli oggetti; l'aleatorietà con cui il processo di Garbage Collection viene schedulato
[10].
4
Un'Architettura per il monitoraggio online
della Java Virtual Machine
Ogni Garbage Collector deve fondamentalmente eseguire due mansioni:
•
Riconoscere gli oggetti da segnalare per la Garbage Collection.
•
Liberare spazio nell'area Heap.
La Garbage Detection tipicamente è eettuata denendo un set di root references e determinando la reachability (raggiungibilità) dai roots; un oggetto
è raggiungibile se esiste almeno un percorso, attraverso i references, dai roots
attraverso cui è accedibile da un programma in esecuzione. Tutti gli oggetti
che sono accedibili dai roots sono considerati live, altrimenti Garbage.
5
Un'Architettura per il monitoraggio online
della Java Virtual Machine
1.2.3 Principali Algoritmi di Garbage Collection
Reference Counting :
è stata la prima strategia di Garbage Collection
implementata nella Hot Spot. In questo approccio, un contatore, detto reference count, è mantenuto per ogni oggetto presente nell'Heap.
Quando
un oggetto viene creato ed un riferimento ad esso viene assegnato ad una
variabile, il counter dell'oggetto viene settato ad uno. Quando un'altra variabile assegna un riferimento a quell'oggetto, il contatore dell'oggetto viene
incrementato; se invece un riferimento ad un oggetto esce dall'ambito della
visibilità oppure è assegnato un nuovo valore, il contatore viene decrementato. Ogni oggetto, il cui contatore è settato al valore zero, viene segnalato per
la garbage collection. La semplicità di implementazione di questa tecnica si
scontra con il difetto di non riuscire ad identicare i cicli di riferimenti, ad
esempio:
Tracing Collectors: investiga il grafo dei riferimenti agli oggetti iniziando
dai nodi roots; gli oggetti, a mano a mano che vengono ispezionati, sono
marcati settando un ag (direttamente nell'oggetto oppure attraverso una
bitmap). Quando il tracing è completo, gli oggetti privi del ag sono segnalati
come unreachable e possono essere garbage collected.
Compacting Collectors:
I Garbage Collectors dovrebbero, tra l'altro,
tentare di combattere il fenomeno della frammentazione dell'area Heap, problema comune alle MMU (Memory Management Unit) dei sistemi operativi
. Due delle strategie maggiormente in voga sono la compattazione e la copia.
Entrambe le tecniche spostano oggetti on the y per ridurre la frammentazione dell'Heap. I Compacting Collectors spostano gli oggetti in stato live
attraverso lo spazio libero nell'heap no alla ne; i riferimenti degli oggetti
spostati vengono poi aggiornati. Uno modo per semplicare l'aggiornamento
dei references è quello di aggiungere un livello di indirizzamento indiretto.
Copying Collectors:
questi collectors invece, spostano tutti gli oggetti, in
stato live, in una nuova area e li pongono spalla a spalla eliminando la
6
Un'Architettura per il monitoraggio online
della Java Virtual Machine
frammentazione della suddetta area; la area precedentemente occupata viene
segnalata come libera.
Una tecnica abbastanza comune, tra i Copying Collectors, è la Stop And
Copy. In questo approccio l'area heap viene divisa in due regioni, di queste,
se ne utilizza solo una alla volta. Quando lo spazio di quella in uso termina,
l'esecuzione del programma viene messa in stop, tutti gli oggetti della prima
regione vengono travasati nella seconda side by side.
Al termine della
copia viene ripresa la normale esecuzione del programma.
Parallel Collectors: The new collectors in JDK 1.4.1 are all designed to
address the issue of garbage collection on multiprocessor systems. Because
most garbage collection algorithms stop the world for some period of time,
a single-threaded garbage collector can quickly become a scalability bottleneck, because all but one processor are idle while the garbage collector has
suspended the user program threads. Two of the new collectors are designed
to reduce collection pause time the parallel copying collector and the concurrent mark-sweep collector. The other, the parallel scavenge collector, is
designed for higher throughput on large heaps.
1.2.4 La Java Virtual Machine ed il Compilatore HotSpot
La Java HotSpot Virtual Machine costituisce l'implementazione Sun della
specica della VM. Alcune delle caratteristiche più interessanti, come visto
in [8, 9] dell'architettura sono:
•
Frame interpretati, compilati e nativi sullo stesso Stack.
•
Preemptive multithreading basato su Thread Nativi.
•
Generational e Compacting Garbage Collection.
7
Un'Architettura per il monitoraggio online
della Java Virtual Machine
I compilatori JIT (Just-in-Time) sono essenzialmente tradizionali compilatori che traducono, il Bytecode Java in codice nativo per il processore host, al
volo. Ovviamente bisogna tenere conto del fatto che, il compilatore JIT, girando a user-time, è soggetto ad un signicante delay che rallenta sicuramente
la fare di startup dell'applicazione. Inoltre va considerato che la compilazione JIT potrebbe avere eetti abbastanza attenuati in Java, rispetto ad altri
linguaggi come C/C++, a cause dei seguenti fattori:
•
In Java tutti gli oggetti vengono allocati nello Heap ( diversamente da
C++, dove alcuni oggetti giacciono nullo stack), da ciò deriva un tasso
medio di allocazioni degli oggetti più alto, se comparato con quello in
C++; va poi tenuto in considerazione che Java è un linguaggio Garbage
Collected, quindi soggetto a diversi tipi di memory allocation overhead.
•
In Java moltissimi metodi sono dichiarati
virtual,
ovvero, potenzial-
mente polimor.
Le dicoltà che sono emerse, vengono risolte dall'architettura HotSpot mediante Adaptive Optimization, attraverso tre tecniche:
•
HotSpot Detection: L'ottimizzazione adattativa risolve i problemi della JIT compilation avvantaggiandosi una interessante proprietà della
maggior parte dei programmi. Virtualmente tutti i programmi passano la maggior parte del tempo di esecuzione eseguendo un piccola parte
del codice. Il compilatore HotSpot analizza il codice, mentre lo esegue,
per localizzare le aree critiche del programma; identicate queste aree
critiche procede ad una ottimizzazione nativa delle stesse per migliorare
le performance.
•
Method Inlining: ha importanti beneci; riduce drammaticamente la
frequenza delle invocazioni dei metodi. Ancora più importante, produce
grossi blocchi di codice su cui è possibile eettuare ottimizzazione.
8
Un'Architettura per il monitoraggio online
della Java Virtual Machine
•
Dynamic Deoptimization: Il caricamento dinamico complica sensibilmente le procedure di inlining, poiché cambia le relazioni globali all'interno del programma (infatti il method inlining è un tecnica di analisi
globale); una classe appena caricata infatti, potrebbe contenere dei
metodi per cui è necessario l'inlining.
La Java Hot Spot VM è ca-
pace di deottimizzare ed eventualmente ri-ottimizzare hot spots
precedentemente ottimizzati.
La Java Virtual Machine prevede due distinti modi di funzionamento:
Client
e
Server ;
entrambi i modi di funzionamento sono simili, ma il modo
server è ottimizzato per l'esecuzioni di applicazioni nel lungo periodo, applicazioni per le quali non è importante una fase di
starup estremamente rapida,
bensì una esecuzione del codice molto rapida. La Client VM, invece, è specializzata nel ridurre i tempi di startup e l'occupazione di memoria; queste
caratteristiche la rendono particolarmente adatta a sistemi tipo client.
1.2.5 Java Threads
In Java la gestione dei thread è eettuata associando i thread del sistema
operativo host con ogni thread della VM in rapporto 1:1 Sia i metodi Java
che quelli nativi condividono lo stesso stack, consentendo chiamate veloci tra
applicazioni Java ed applicazioni C. I thread java sono fully preemptive, e
supportati dal meccanismo di schedulazione del sistema operativo host. Uno
dei vantaggi fondamentali dell'uso di thread nativi, e della loro schedulazione,
è poter sfruttare le caratteristiche di multithreading, del sistema operativo,
in modo trasparente.
1.2.6 Execution Engine
La specica della Java Virtual Machine impone che ogni implementazione della specica, deve avere l'abilità di eseguire il bytecode Java (bc). La
9
Un'Architettura per il monitoraggio online
della Java Virtual Machine
Figura 1.2: Execution Engine
tecnica più semplice che è possibile pensare, implementata nella prima JVM
della SUN, è l'interpretazione del bytecode one a time; un'altra tecnica
di esecuzione è la cosidetta
JIT (Just-In-Time) Complilation.
La tenden-
za, nell'ultimo periodo, è rivolta ad un approccio combinato, ibrido, detto
Adaptive Optimization.
Una Virtual Machine con ottimizzatore adattativo
inizia ad eseguire una applicazione Java interpretando tutto il codice, ma
monitorando l'esecuzione dello stesso. La maggior parte dei programmi passa il novanta percento del proprio tempo eseguendo circa il dieci percento del
codice; monitorando l'esecuzione dell'applicazione, la virtual machine riesce
a determinare quali sono gli Hot Spot. Nel momento in cui la VM stabilisce
che un metodo è un Hot Spot avvia un thread incaricato di compilare il bytecode, di detto metodo, in nativo e di ottimizzarlo sfruttando alcune delle
tecniche osservate in precedenza .
Il fatto di selezionare dinamicamente il
codice da compilare in nativo, costituisce in ogni caso una miglioria rispetto
alla compilazione JIT tradizionale.
10
Un'Architettura per il monitoraggio online
della Java Virtual Machine
1.2.7 Error Handling
Il momento ideale per catturare un errore è a tempo di compilazione;
sfortunatamente non tutti gli errori possono essere riscontrati in questa fase.
Con il termine Exception si intende Exceptional Event;
una eccezione è un evento che si manifesta durante l'esecuzione di un
programma alterando il normale usso d'esecuzione dello stesso.
Quando
si manifesta un errore in un metodo, la VM crea un oggetto d'eccezione
e lo passa al sistema di runtime.
Tale oggetto contiene informazioni circa
l'errore, includendo il tipo e lo stato del programma. Nel momento in cui un
metodo solleva una eccezione, il runtime system cerca di trovare qualcosa per
gestirlo scorrendo la lista, ordinata, dei metodi che hanno portato il sistema
nello stato di errore; questa lista è detta Call Stack. Il runtime system cerca,
ricorsivamente, nel call stack, un metodo che contenga un blocco di codice che
possa gestire l'eccezione. Questo blocco è detto Exception Handler. Quando
viene identicato un handler opportuno, il runtime system passa l'eccezione
all'handler. Se, dopo una ricerca esaustiva attraverso tutti i metodi sul call
stack, non è possibile rintracciare un handler opportuno, viene invocato il
Thread.dead, il programma termina e la VM va nello stato VMDeath
1
.
L'uso delle Eccezioni nella gestione degli errori è indubbiamente vantaggiosa, infatti permette di:
•
Separare il codice di gestione degli errori dal regular code.
•
Propagare gli errori in cima al call stack.
•
Raggruppare e dierenziare i diversi tipi di errori.
Una distinzione delle eccezioni presenta questa struttura:
1 Si
veda in proposito la documentazione relativa a JVM TI: Java Virtual Machine Tool
Interface, ove vengono descritti gli eventi principali atti a descrivere il comportamento e
lo stato della JVM.
11
Un'Architettura per il monitoraggio online
della Java Virtual Machine
(a)
(b)
1.3 Classicazione dei Fallimenti della Java
Virtual Machine
•
Checked Exception:
sono condizioni eccezionali che un applicazione
well-written dovrebbe anticipare e recuperare. Le Checked Exception
sono soggette al vincolo del Catch/Specify; tutte le eccezioni sono
checkd tranne quelle indicate da Error, RuntimeException e le loro
sottoclassi.
•
Errori: sono quelle condizioni eccezionali esterne all'applicazione, e che
tipicamente l'applicazione non può né anticipare né recuperare.
•
Runtime Exception: sono quelle condizioni eccezionali interne all'applicazione e che l'applicazione non può né anticipare né recuperare. Sono
un sintono di un bug della programmazione, inteso come errore logico,
oppure dovuto ad un errato uso delle API. Errors e Runtime Exceptions
sono dette anche Unchecked Exception.
L'unica sorgente di informazioni circa i Fallimenti della Java Virtual Machine, attualmente disponibile, è il Bug Database, disponibile su http://bugs.sun.com
. Le informazioni, in esso contenute, sono state fornite da utenti, nonostante
questo, è possibile dimostrare che come costituisce un punto di inizio per
12
Un'Architettura per il monitoraggio online
della Java Virtual Machine
un'analisi dei fallimenti della JVM. Anzitutto è fondamentale stabilire dei
criteri attraverso cui analizzare i dati, e quindi classicare i fallimenti sulla base di questi.
Una possibilità è la seguente; distinguere, secondo [5], i
fallimenti in:
13
Un'Architettura per il monitoraggio online
della Java Virtual Machine
Figura 1.3: Fault Classication
14
Un'Architettura per il monitoraggio online
della Java Virtual Machine
Failure Manifestation:
•
VM Error Message: Eccezioni sollevate da un programma Java.
•
OS Error Message: Errori del livello Sistema Operativo riportati all'utente, magari sotto forma di SIGNAL.
•
Hang / Deadlock: Sono quegli scenari che non portano ad un crash
della JVM, ma l'applicazione in esecuzione, oppure una sua parte, va
in stallo.
•
Silent Crash: La JVM entra nello stato VMDeath senza alcun report
all'utente.
•
Computation Error: I risultati ottenuti sono diversi da quelli attesi.
Failure Source:
•
Execution Unit
•
OS Virtualizazion Layer
•
Memory Management Unit (MMU)
•
System Service Unit
Severity:
•
Un fallimento è denito catastroco se porta al crash della JVM.
•
Un fallimento è detto non catastroco se la JVM continua nella sua
esecuzione nonostante il fallimento.
Environment:
Sono quei fallimenti classicati in base alla correlazione
della JVM con l'ambiente in cui essa è in esecuzione.
VM Activity: E' la classicazione dei fallimenti correlata al workload cui
è sottoposta la JVM.
Failure Frequency: E' la classicazione stilata sulla base della frequenza
con cui un certo fallimento occorre.
15
Un'Architettura per il monitoraggio online
della Java Virtual Machine
1.4 Conclusioni
Come gia menzionato, la piattaforma Java ormai è presente in una moltitudine di contesti, alcuni dei quali particolarmente critici; E' stata anche
ricordata la mancanza di supporto diretto alla Fault Tollerance; il connubio
di questi due fattori, genera un problema grave e che urge arontare: Valutare il grado di robustezza della Java Virtual Machine e delle applicazioni
Java.
16
Capitolo 2
Il Problema del Monitoraggio nel
Contesto della Java Virtual
Machine
2.1 Metodologie per lo Studio di Adabilità
Applicazioni speciche per il monitoraggio di un paziente, per il controllo
di processo, per l'online transaction processing, possiedono una forte connotazione di
continuità di servizio ;
Ciò è ancora più vero se consideriamo che
statisticamente i periodi di non funzionamento sono concentrati durante il
picco di domanda. Queste applicazioni richiederebbero sistemi, virtualmente, infallibili, o ciò che è lo stesso, che parti del sistema possano fallire senza
compromettere la disponibilità di fornire il servizio.Risulta ovvio quindi, che
negli ultimi decenni lo studio dell'adabilità dei sistemi informatici ha rivestito un ruolo primario sia nella comunità scientica che industriale.
In
particolare una analisi condotta da Jim Grey, e presentata in [4], dimostra
come la percentuale più alta di fallimenti in un sistema sia dovuta al software
e non al resto della piattaforma. La stessa analisi mostra lo stesso risultato
17
Un'Architettura per il monitoraggio online
della Java Virtual Machine
anche in termini di
MTBF (Mean Time Between Failures ),
in Figura
2.1
una sintesi dei risultati.
Figura 2.1: Analisi dei Fallimenti del Tandem System
Nel corso degli anni sono state sviluppate diverse metodologie per lo
studio dei sistemi informatici esposti di seguito.
18
Un'Architettura per il monitoraggio online
della Java Virtual Machine
2.1.1 Measured-based Analysis
Questa tecnica è in assoluto la più famosa tra i lavori connessi alla dependability analysis; l'idea è molto semplice: monitorare un sistema per ottenere
dati circa i suoi fallimenti. Nella letteratura sono stati proposti molti modelli
utili per modellare l'adabilità dei sistema sotto analisi partendo dai dati
raccolti circa i fallimenti; in questi lavori è mostrato anche come, partendo
dai suddetti dati, è possibile creare modelli sui fallimenti e sull'adabilità.
Esempi di questa tecnica sono presenti in letteratura, come in [11]. Tuttavia
questa tecnica non è particolarmente adatta allo studio dell'adabilità della
Java Virtual Machine, non perchè il monitoraggio della JVM sia complesso,
ma bensì, perchè i tempi di osservazione necessari a raccogliere un suciente
numero di stati con livelli di workload ordinari sarebbero troppo elevati .
Per poter utilmente sfruttare questa tecnica, dovrebbero essere pensati dei
workloads specici, attività troppo complessa, soprattutto considerando che
esistono numerose soluzioni diverse.
2.1.2 Dependability Benchmarking
Il concetto di Dependability Benchmarking si allontana sostanzialmente da quello di Performance Benchmarking; in questi ultimi, diversi tipi di
workloads
sono imposti sul sistema da testare. Una apposita unità di moni-
toraggio fornirà i dati di misura estratti dagli esperimenti. Nei dependability
benchmarks invece, al sistema vengono imposti sia un set di workloads che
di
faultloads
vedi [7]. Ogni esperimento è costituito dall'imposizione di un
workload, per simulare una condizione di funzionamento del sistema, e dall'iniezione di un fault estratto da un certo set di faultload. Tipicamente ogni
dependability benchmark è costituito dai seguenti elementi vedi Figura
•
Benchmark Target : su cui vengono eettuate le misure
•
Fault Injection Target : dove vengo iniettati i fault
19
2.2:
Un'Architettura per il monitoraggio online
della Java Virtual Machine
•
Workload : per imporre una condizione di funzionamento
•
Faultload : per portare il sistema in uno stato di cattivo funzionamento
•
Monitor : per estrarre le misure dell'esperimento
•
Analyzer : per fare analisi dei risultati ottenuti.
Figura 2.2: Dependability Scenario
I workloads, eseguiti sul sistema da testare, sono alterati con uno dei fault
estratto dal set di faultloads. Le misure di adabilià, come la percentuale di
fallimento, sono estratti dal target a mezzo dell'unità di monitoraggio. Un
esempio molto rilevante di Dependability Benchmark è sicuramente DBench,
un framework sviluppato per valutare l'adabilità di sistemi COTS e COTSbases.
Il
Dependability Benchmarking
sembrerebbe essere interessante per stu-
diare l'adabilità di una virtual machine; diversamente da altri sistemi software, l'adabilità di una macchina virtuale è compromessa sia dalle applicazioni che vengono eseguite su di essa, sia dal sistema operativo host come
si evince dalla Figura
2.3.
Risulta chiaro che esiste un nuovo scenario di analisi: ci saranno misure
di robustezza sull'applicazione e di sensibilità del sistema operativo, ottenute
attraverso:
1.
Fault Injections
nell'applicazione in esecuzione sulla virtual machine
20
Un'Architettura per il monitoraggio online
della Java Virtual Machine
Figura 2.3: Virtual Machine
2.
Monitoraggio
dei risultati
Esperimenti mostrano che i sistemi di gestione delle eccezioni e di verica, built-in nella Java Virtual Machine, sono in grado di tollerare condizioni
di funzionamento anomale dell'applicazione che è in esecuzione.
L'analisi
quindi, si deve spostare sul layer sottostante e alle interazioni tra la virtual
machine e sistema operativo. Anche in questo caso risulta lampante l'importanza di possedere una unità di monitoraggio capace di mostrare questi dati,
ed un modulo di analisi capace di interpretarli.
2.1.3 Error Injection
Un approccio, teso ad accelerare il processo di fallimento, potrebbe essere
iniettare errori al posto dei faults; questa tecnica però, richiede un esatto
mapping tra i software faults ed gli errori.
Deve essere assicurato che gli
errori iniettati emulino esclusivamente i faults di origine software e non rispecchino fallimenti dell'hardware [6]. Le tecniche basate su
Fault Emulation
attraverso error injection garantiscono che l'errore iniettato sia rappresentativo di un fault. A causa della natura qualitativa delle fonti (essenzialmente i
bug databases), è dicile denire un prolo di error injection coerente con l'
Orthogonal Defect Classication
(ODC); un ricerca approfondita nei bug da-
21
Un'Architettura per il monitoraggio online
della Java Virtual Machine
tabases non è stata suciente ad identicare sorgenti di fault che interessassero direttamente la Java Virtual Machine. Sarebbe necessario sviluppare una
nuova classicazione
ad hoc
per il caso della Java Virtual Machine; va notato
però, che una analisi di questo tipo, risulterebbe applicabile esclusivamente
a detto caso e dicilmente riapplicabile ad altri sistemi software.
2.1.4 Robustness Testing
L'idea di
Robustness Testing
Siewiorek e Koopman in [2].
supposto
bug free,
possa fallire.
è stata proposta per la prima volta da
L'idea di base è che un sistema software,
Questo approccio consiste nel fatto che è
praticamente impossibile testare ogni singolo modulo di un sistema software,
con ogni possibile dato di ingresso.
Testing è
Un esempio di Tool per il Robustness
Ballista ; sostanzialmente il testing avviene in quattro fasi:
1. Selezione del modulo da testare
2. Identicare l'iterfaccia (servizio) esposta da questo modulo
3. Forzare il modulo selezionato con dati di ingresso validi e non
4. Analizzare il comportamento del sistema, nel caso di Ballista, del sistema operativo.
Il Robustness Testing sembra interessante nel contesto della analisi di
adabilità della Java Virtual Machine; Per eettuare questo tipo di test
bisognerebbe: identicare le interfacce tra le applicazioni Java e la virtual
machine (quindi il
ByteCode Interpreter
e la
Native Interface ).
L'idea, so-
stanzialmente corretta, si scontra però con un problema di natura implementativa; la tecnica di collaudo esposta da Siewiorek non tiene conto dello stato
interno del sistema da testare, è sostanzialmente un collaudo
black-box,
rende la tecnica non adatta al contesto della Java Virtual Machine.
base di questa considerazione è possibile fare almeno due osservazioni:
22
ciò
Sulla
Un'Architettura per il monitoraggio online
della Java Virtual Machine
1. Bisogna Monitorare lo stato della Java Virtual Machine
2. E' necessario realizzare una forma di collaudo tipo
white-box ; una buona
soluzione potrebbe essere una mirata bytecode injection.
2.1.5 Aging Analysis
Il termine
Software Aging
è stato introdotto da Kintala e Huang nel 1997
in []. Il fenomeno dell'invecchiamento si verica quando alcune risorse utilizzate da una applicazione sono soggette a
depletion
(utilizzate no in fondo):
Esempi classici di questo fenomeno sono i memory leaks, buer overows,
oating point errors.
Un
Aging Fault
viene attivato diverse volte a cau-
sa dell'accumularsi degli errori, questo porta ad un fallimento del sistema.
Gray in [4] espone una classicazione degli aging faults dove esistono:
•
HeisenBugs: sono quei bugs che scompaiono durante la loro ricerca a
causa anche di piccolissime perturbazioni introdotte dal bugcathcer
•
BohrBugs: sono quei bugs che portano sempre il sistema in uno stato di
cattivo funzionamento allorchè una certa operazione viene eettuata.
Studi di invecchiamento su server SOAP-based sono stati proposti da Madeira in [3]; un altro caso di studio interessante circa l'aging in un webserver è
proposto in [1]; in questo studio viene proposto un modello numerico attraverso cui predire il momento più adatto per bloccare il sistema, ed assicurasi
quindi il ringiovanimento dello stesso. Nel corso del tempo diverse metodologie sono state messe a punto per identicare il fenomeno dell'aging e per
prevdere il
Time-To-Resuorce-Exhaustion.
Nel caso specico della Java Vir-
tual Machine è possibile arguire che i componenti interessati dal fenomeno
dell'aging sono: il Runtime System, il Reference Handler ed il Compilatore
JIT e nell'interfaccia con il sistema operativo.
23
Un'Architettura per il monitoraggio online
della Java Virtual Machine
2.2 Tecniche e Tecnologie per il Monitoraggio
Le principali tecnologie disponibili per eettuare il monitoraggio di applicazioni Java, e della stessa Virtual Machine sono: JVM TI (Java Virtual
Machine Tool Interface), JMX (Java Management Extensions) e le tecniche
BCI (ByteCode Injection).
2.2.1 Java Virtual Machine Tool Interface
TM
La JVM
Tool Interface è una interfaccia di programmazione utilizzata
dai tools di sviluppo e monitoraggio. Fornisce dei meccanismi per sondare
lo stato e controllare l'esecuzione di applicazioni Java
TM
.
JVM TI è una
inferfaccia two-way; un client JVM TI, d'ora in avanti chiamato
essere informato di interessanti occorrenze attraverso
eventi.
terrogare e controllare l'applicazione attraverso molteplici
risposta ad eventi ( e vengono dette
callbacks
agent 1 , può
JVM TI può in-
funzioni, alcune in
), altre indipendenti da questi.
Gli agenti vengono eseguiti nello stesso processo e comunicano direttamente
con la Virtual Machine che sta eseguendo l'applicazione da esaminare.
comunicazione avviene attraverso una interfaccia nativa.
La
Tipicamente gli
agenti sono abbastanza compatti, e possono essere controllati da un processo
separato senza interferire con la normale esecuzione dell'applicazione target.
Un tool di monitoraggio può essere scritto utilizzando direttamente JVM TI,
oppure attraverso una interfaccia di alto livello.
Gli agenti possono essere
scritti in qualsiasi linguaggio nativo che supporti le convenzioni per le chiamate del linguaggio C e le denizioni C/C++. Le denizioni delle funzioni,
tipi di dato, eventi e costanti sono contenute nell'header le jvmti.h; per
utilizzarle è suciente aggiungere:
1 Di
un agente JVM TI viene eettuato il Deploying nella forma di Shared Library: .so,
.dylib ...
24
Un'Architettura per il monitoraggio online
della Java Virtual Machine
#include <jvmti.h>
al proprio codice.
La libreria, ossia l'agente, deve esportare una funzione di start-up con il
seguente prototipo che costituisce una sorta di entry-point per la libreria:
JNIEXPORT jint JNICALL
Agent_OnLoad(JavaVM* vm, char* options, void* reserved)
Questa funzione sarà invocata dalla VM al momento del caricamento
della libreria. La VM dovrebbe caricare la libreria durante le primissime fasi
dell'inizializzazione della VM, quando:
•
le system properties vengano impostate
•
l'intero set di capabilites è ancora raggiungibile
•
nessun bytecode è stato eseguito
•
nessuna classe è stata caricata
•
non è stato creato nessun oggetto
2
Il valore di ritorno da
Agent_onLoad è utilizzato per segnalare un errore.
Ogni valore diverso da zero indica un errore e causa la terminazione della
VM.
La libreria potrebbe, opzionalmente, esportare una funzione di shutdown
con il seguente prototipo:
JNIEXPORT void JNICALL Agent_OnUnload(JavaVM* vm)
Questa funzione verrà chiamata dalla VM quando la libreria sta per essere
scaricata; è da notare la sostanziale dierenza tra la chiamata di questa
funzione e l'occorrenza dell'evento
VM_Death.
2 le
Anchè l'evento
VM_Death
Capabilities, utilizzate per impostare la VM, sono disponibili solo in questa fase del
ciclo di vita della VM.
25
Un'Architettura per il monitoraggio online
della Java Virtual Machine
venga inviato, la VM deve aver completato la propria inizializzazione , deve
esistere un environment JVM TI valido che abbia impostato una callback
per questo evento, e che l'evento sia stato abilitato.
necessarie invece per la funzione
Agent_OnUnload e,
Queste fasi non sono
tra l'altro, questa viene
invocata anche se la libreria viene scaricata per altre ragioni.
La Specica JVM TI supporta l'uso di più agenti simultaneamente. Ogni
agente possiede il proprio environment; ciò implica che lo stato JVM TI è
separato per ogni agente (i cambiamenti ad un ambiente non condizionano
gli altri). Lo stato di un ambiente JVM TI comprende:
•
Event Callbacks
•
un set di eventi abilitati
•
le Capabilities
•
memory allocation / deallocation hooks
Nonostante gli stati JVM TI siano separati, gli agenti ispezionano e modicano lo stato condiviso della VM, quindi anche l'ambiente nativo nel quale
sono in esecuzione; ciò di ripercuote sul fatto che un agente potrebbe perturbare i risultati di altri agenti o portarli al fallimento. La specica quindi,
delega al programmatore la responsabilità di evitare stati di funzionamento
anomali dovuti all'interazione di più agenti.
L'interfaccia JVM TI fornisce supporto diretto alla
mentation,
ByteCode Instru-
ossia all'abilità di alterare le istruzioni bytecode della Java Vir-
tual Machine che formano il programma.
Tipicamente queste alterazio-
ni consistono nell'aggiunta di eventi al codice - body - di un metodo; ad
esempio l'aggiunta, all'inizio del corpo di un metodo, di una chiamata a
myAgent.methodEntered().
Fino a quando le modiche sono puramente
additive, queste non modicano ne lo stato ne il comportamento dell'applicazione.
Considerando che il codice aggiunto è standard bytecode, la VM
26
Un'Architettura per il monitoraggio online
della Java Virtual Machine
può eettuare su di esso, come sul resto del codice dell'applicazione, ogni ottimizzazione che ritenga opportuna. L'instrumentazione può essere iniettata
in uno di questi tre modi:
•
Static Instrumentation:
Il class le è instrumentato prima di essere
caricato nella VM; questa tecnica è fortemente sconsigliata.
•
Load-Time Instrumentation: Quando un class le è caricato dalla VM,
i raw bytes del class le sono inviati, per l'instrumentazione, all'agente.
L'eveno
•
ClassFileLoadHook
fornisce questa funzionalità.
Dynamic Instrumentation: Una classe gia caricata, è modicata. Questa funzionalità è fornita dalla funzione
RedefineClasss.
In Appendice viene presentato un semplice esempio di agente JVM TI
SimpleAgent.dylib ) che illustra le funzionalità esposte.
(
2.2.2 Java Management eXtensions
La Java Management Extensions (JXM) API è uno standard per la gestione ed il monitoraggio do applicazioni e servizi. Denisce una architettura
di gestione, dei patterns di progettazione, delle APIs e dei servizi per progettare soluzioni web-based, distribuite, dinamiche e modulari, atte a gestiore
risorse Java. La tecnologia JMX è nativa al linguaggio Java; ore estensioni
di gestione ecienti e leggere a funzioni Java. Consiste di un set di speciche e tools di sviluppo per gestire ambienti Java e progettare soluzioni di
gestione e monitoraggio per applicazioni e servizi. Fornisce funzionalità di
Instrumentazione del codice, un modo semplice e standard di scrivere smart
agent, di integrazione con middleware e sistemi di monitoraggio preesistenti.
Usi tipici della tecnologia JMX sono:
•
Consultare e modicare la congurazione di una applicazione
27
Un'Architettura per il monitoraggio online
della Java Virtual Machine
•
Raccogliere statistiche circa il comportamento di una applicazione e
rendere queste disponibili.
•
Noticare cambiamenti di stato e condizioni di errore.
28
Un'Architettura per il monitoraggio online
della Java Virtual Machine
Figura 2.4: JMX Tiered Architecture
La tecnologia JMX è denita da una architettura multi-tier dove le risorse
gestite e le applicazioni di gestione sono integrate di un approccio plug and
play come mostrato in Figura 2.4.
Una certa risorsa è instrumentata attraverso uno o più oggetti Java conosciuti come Maneged Beans (MBeans), registrati in un oggetto server noto
come MBean server.
In dettaglio:
Il livello instrumentazione contiene gli
MBeans e le loro risorse da gestire. Fornisce una specica per implementare
risorse gestibili attraverso JMX; una risorsa è manageble se è sviluppata in
Java (oppure possiede un Java wrapper) ed è stata instrumentata in modo
tale da poter essere gestita da un applicazione JMX-compilant. Una risorsa
può essere instrumentata in due modi: staticamente e dinamicamente; gli
Standard MBeans sono oggetti Java conformi ad un denito pattern di programmazione (ad esempio devono avere un costruttore e dei metodi setters
e getters). Un Dynamic MBean è conforme ad una interfaccia specica che
ore più essibilità a runtime.
Le componenti chiave a livello instrumentazione sono gli MBeans, il modello di notica e le MBeans metadata classes.
29
Un'Architettura per il monitoraggio online
della Java Virtual Machine
• Standard MBeans:
il più semplice da progettare ed implementare.
La loro interfaccia di gestione è denita dal nome dei metodi.
• Dynamic MBeans:
Implementano una specica interfaccia ed espon-
gono la loro interfaccia di gestione a runtime.
• Notication Model:
JMX denisce un generico modello di notica
basato sul modello degli eventi Java.
• MBeans Metadata Classes:
Queste classi contengono le strutture
per descrivere tute le complonenti di una interfaccia di gestione di un
MBean: attributi, operazioni, notiche e costruttori.
Il livello Agente contiene gli agenti JMX usati per esporre gli MBeans.
Fornisce una specica per implementare gli agenti, i quali controllano le risorse e le rendono disponibili alla gestione da applicazioni remote. Gli agenti
JMX sono costituiti da un MBean server e da un set di servizi per controllare gli MBeans. I JMX Managers accedono agli agenti MBeans tramite un
protocol adapter (RMI, SNMP, HTTP ....). Le componenti fondamentali di
un agente sono l'MBean server ed i Services:
• MBean server:
Un registro di oggetti che sono esposti alle operazioni
di gestione in un agente.
• Agent Services:
Oggetti che possono eettuare operazioni di gestione
su di un MBean registrato sul server. I seguenti agenti sono disponibili
in J2SE 5.0:
Dynamic Class Loader:
Attraverso un servizio m-let riceve
ed instanzia nuove classi e librerie native da arbitrarie locazioni
remote.
Monitors:
Osserva il valore di un attributo degli MBeans e può
noticare altri oggetti di cambiamenti di stato.
30
Un'Architettura per il monitoraggio online
della Java Virtual Machine
Timers:
Forniscono un meccanismo di scheduazione.
Relation Service:
Denisce le associazioni tra MBeans e forza la
cardinalità della relazione basandosi su tipi predeniti di relazioni.
Il livello Manager contiene le componenti che abilitano le applicazioni
di gestione a comunicare con gli agenti JMX. Fornisce le interfaccie per
implementare JMX Managers.
In Appendice viene presentato un semplice esempio di agente JMX che
illustra le funzionalità esposte.
2.2.3 Bytecode Injection
La
Bytecode Transformation
è una potente tecnica per estendere od adat-
tare, dinamicamente, software Java utilizzando dei
tranformers
che automa-
ticamente riscrive classi compilate. i Bytecode Transformers sono utilizzati
per:
•
Integrare componenti software sviluppati separatamente
•
Instrumentare applicazioni a scopo di monitoraggio, debugging o proling
•
Estendere applicazioni con nuove funzionalità di sicurezza, controllo ed
altro
I transformers modicano software esistente attraverso un parsing automatico e la manipolazione di classi compilate rappresentate da
classle.
Le
nuove funzionalità, che si vuole aggiungere al software, sono completamente disaccoppiate dall'applicazione target ed implementate direttamente nel
trasformatore. Considerando che i Bytecode Transformers agiscono sui programmi compilati, sono in grado di consentire le cosiddette
/ extensions
late adaptations
nonstante il codice sorgente non sia disponibile.
31
Esperimenti
Un'Architettura per il monitoraggio online
della Java Virtual Machine
mostrano le enormi potenzialità delle tecniche di bytecode injection anche,
e soprattutto in sistemi software network-oriented.
Nonostante il successo
delle ricerche, ci sono alcuni aspetti critici che ostacolano l'accettazione delle
tecniche BCI come strumento di sviluppo: La costruzione dei trasformatori richiede: una conoscenza estremamente approfondite della struttura della
Java Classle Rappresentation e del ByteCode Instruction Set, la stesura
di un insieme di regole che disciplinino la scrittura di detti trasformatori in
modo che l'inserimento del bytecode sia sicura.
Sorge il problema adesso,
di stabilire quando eettuare l'inserimento del codice; per questo scopo sono
state individuate tre casi:
1. Staticamente: i classle vengono modicati attraverso BCI prima di
essere caricati nella Java Virtual Machine dal ClassLoader.
2. Load Time:
con questo approccio i classle vengono modicati pri-
ma che la Java Virtual Machine possa accedere alla classe; una buona
soluzione per implementare questa strategia è progettare un apposito
ClassLoader che integri un trasformatore, ciò garantisce la correttezza
del classle caricato nella virtual machine; questa strategia infatti, ha
il vantaggio di servirsi, automaticamente, del meccanismo di verica
del Classloader vedi Figura
2.5.
Figura 2.5: Trasformazione di classi a Load Time
32
Un'Architettura per il monitoraggio online
della Java Virtual Machine
3. Full Dynamic: con questo approccio è possibile modicare una classe
potenzialmente gia eseguita dalla Java Virtual Machine, attraverso la
tecnica dell'HotSwapping; modiche puramente additive, non creano
nessuna alterazione nel comportamento della Java Virtual Machine e
di tutto il software in esecuzione su di essa.
La letteratura propone moltissime librerie per la bytecode injection, le più
utilizzate sono sicuramente: BCEL (ByteCode Engeneering Library) e Javassist. Nel panorama delle librerie per BCI, tardano ad aacciarsi soluzioni di
basso livello sulla falsa riga di
java_crw_demo.
33
Un'Architettura per il monitoraggio online
della Java Virtual Machine
2.3 Tools di Monitoraggio
Esistono, nel panorama del software commerciale e non, delle soluzioni per
il monitoraggio della Java Virtual Machine; nel corso della prossima sezione
esamineremo due utilities: JProler e HPROF. Lo scopo è mostrare come
questi tools non siano adatti ad un monitoraggio orientato alla raccolta di
dati per scopi di Dependability Analysis.
2.3.1 ej-Technologies, JProler
JProler è un software commerciale orientato in modo particolare alla
analisi delle performance di una applicazione in esecuzione sulla Java Virtual
Machine. Si basa sostanzialmente su JVMPI (Java Virtual Machine Proling
Interface) e JVMTI (Java Virtual Machine Tool Interface); questa scelta
è stata eettuata per garantire compatibilità con le virtual machine 1.4.x.
L'instrumentazione completa non è possibile sulle virtual machine 1.5.x, dove
l'interfacca di proling è divenuta
In Figura
deprecated;
2.6 alcuni esempi d'uso di JProler:
34
Un'Architettura per il monitoraggio online
della Java Virtual Machine
(a) Proling Settings
(b) Memory Usage
Figura 2.6: Esempi di funzionamento di JProler
2.3.2 Sun Microsystems, HPROF: Heap Proler
Heap Proler è essenzialmene una piccola demo fornita con i sorgenti
della Java Virtual Machine allo scopo di dimostrare l'utilizzo dell'interfaccia
JVMTI e le possibilità di instrumentazione fornite da java_crw_demo; tipicamente il suo uso è correlato a quello di un tool di visualizzazione dell'heap
dump (Hat).
35
Un'Architettura per il monitoraggio online
della Java Virtual Machine
2.4 Conclusioni
E' possibile fare alcune considerazioni su quanto esposto; anzitutto, è
chiaro, a questo punto, che eettuare delle misure di adabilità è necessario
avere a disposizioni una unità di monitoraggio specica per questo scopo;
ancora, gli strumenti di monitoraggio e proling esistenti sono insucienti
allo scopo. Vediamo perchè sono insucienti:
•
Tutti gli strumenti di proling esistenti sono basati sull'interfaccia JVMTI e, per motivi di backward compatibility, su JVMDI (JVMPI); l'instrumentazione del bytecode avviente tramite il metodo
RedefineClasses,
dove:
typedef{
jclass klass;
jint class_byte_count;
const unsigned char* class_bytes;
}jvmtiClassDefinition;
RedefineClasses(jvmtiEnv* Env,
jint class_count,
const jvmtiClassDefinition* class_definition)
Attraverso il suddetto metodo, un classe viene sostituita da una sua
copia, instrumentata da codice arbitrario; il bytecode della classe deve
essere prodotto in qualche modo (ricordiamo che le interfaccie JVMDI,
JVMPI e JVMTI sono in linguaggio C / C++) e fornite all'agente.
Un modo per leggere e scrivere il bytecode di una classe è utilizzare
ja-
va_crw_demo di Kelly O'Hair; sfortunatamente la suddetta utility non
36
Un'Architettura per il monitoraggio online
della Java Virtual Machine
permette di instrumentare il metodo
finalize,
attività fondamentale
per analizzare l'area heap della VM.
•
Le informazioni raccolte da questi tools, sono principalmente a carattere quantitativo, inutili per studiare l'adabilità della Java Virtual
Machine.
•
Non è possibile denire una instrumentazione
ad hoc per delle speciche
situazioni; questo rende questi software poco modulari e scalabili.
•
In particolare HPROF è uno strumento che fornisce informazioni esclusivamente sull'area di memoria heap eettuando delle iterazioni complete dell'heap (a volte questa tecnica è detta
do dei
tags
sugli oggetti.
heap walkin' )
e ponen-
L'instrumentazione del codice è eettuata
attraverso chiamate native alla libreria
37
java_crw_demo.
Capitolo 3
JVMMon: Un Sistema di
Monitoraggio Orientato alla
Diagnosi
3.1 Introduzione a JVMMon
E' stata ampiamente motivata la necessità di disporre di un ambiente di
monitoraggio dedicato per eetture una analisi di adabilità della Java Virtual Machine; studiando l'approccio, le tecniche ed i risultati di altri software
di proling / monitoring, ci si è resi conto che, per perseguire lo scopo presso, bisogna conoscere lo stato della JVM in relazione agli eventi sollevati
al suo interno. Per monitorare lo stato interno della Java Virtual Machine
è stato sviluppato JVMMon (Java Virtual Machine Monitoring System), un
sistema di monitoraggio distribuito basato sulle tecnologie JMX (Java Managemente eXtensions), JVMTI (Java Virtual Machine Tool Interface) e sulle
tecniche di ByteCode Injection (BCI).
38
Un'Architettura per il monitoraggio online
della Java Virtual Machine
Le caratteristiche principali di tale sistema sono:
1. Esecuzione trasparente di applicazioni Java su Virtual Machine instrumentate
2. Monitoraggio On-Line, ossia l'unità di data analysis non è vincolata,
per processare i risultati del monitoraggio, ad attendere la terminazione
dell'applicazione test.
3. Impatto contenuto sulle performance della JVM
3.2 Funzionalità Oerte
Per riuscire a perseguire gli scopi pressati, ossia progettare una unità di
monitoraggio della Java Virtual Machine che fornisca dati utili per analisi
di adabilità, sono state individuate alcune aree di interesse su cui porre
l'attenzione, in particolare la necessità di fornire servizi di:
1. Monitoraggio del ciclo di vita della Virtual Machine
Figura 3.1: Java Virtual Machine Heap
2. Monitoraggio dell'area Heap , vedi Figura
39
3.1
Un'Architettura per il monitoraggio online
della Java Virtual Machine
1
•
Creazione e nalizzazione
•
Garbage Collection
di oggetti
3. Gestione e sincronizzazione dei thread
• Contention
•
dei
mutex
e delle
condition variables
Gestione dei thread
4. Monitoraggio delle risorse utilizzate dalla Virtual Machine e dalle applicazioni in esecuzione su di essa
•
Monitoraggio dei File Descriptors (apertura, chiusura)
•
Monitoraggio delle Socktes (apertura, chiusura)
5. Monitoraggio dello stack delle chiamate ai metodi per ogni thread.
I dati raccolti dall'unità di monitoraggio devono poi essere resi disponibili ad un sistema di data analysis che riesca ad interpretarli e fornire una
diagnostica della Virtual Machine monitorata.
3.3 Architettura
In Figura
3.2 è mostrata l'archiettura di JVMMon
L'agente JVMTI, sulla Virtual Machine monitorata, riceve le informazioni
circa lo stato della JVM intercettando gli eventi sollevati dalla stessa; Le
informazioni raccolte vengono inviate al
stato della
Monitored Virtual Machine.
sullo stesso sistema host.
Local MonitorDaemon che elabora lo
Questi due componenti sono presenti
Ogni evento sollevato dalla JVM monitorata è
anche registrato su di un le di log locale al lesystem.
1
40
Ancora, l'agente
Un'Architettura per il monitoraggio online
della Java Virtual Machine
HeartBeat )
JVMTI invia periodicamente un segnale (
per avvisare il Local
Monitor Daemon di eventuali fallimenti.
Ancora, il Local MonitorDaemon norica al
Data Collector
i fallimenti e
i cambiamenti rilevanti nello stato della VM monitorata. Gli eventi sono salvati in un apposito database (
Event Database ), mentre ogni volta che lo stato
della JVM subisce dei cambiamenti rilevanti una snapshot dello stesso viene
salvata nell'
State Snapshot Database.
Per Cambiamento Rilevante inten-
diamo: cambiamento del modo di funzionamento (da
Normal
Handling,
live,
una variazione nel numero di thread in stato
un numero di
Context Switch,
a
Exception
viene segnalato
in applicazioni multithread, superiore ad una
Figura 3.2: Architettura di JVMMon
41
Un'Architettura per il monitoraggio online
della Java Virtual Machine
Column
Description
Component
Compomente della JVM interessanto dall'evento
Timestamp
Timestamp dell'evento
Event
Nome dell'evento
Data
Informazioni speciche dell'evento
Tabella 3.1: Formato del Local Log File dell'agente JVMTI
soglia ssata, variazioni signicative dell'area Heap disponibile. Analizziamo
adesso in dettaglio le politiche adottate da ciascun componente esaminato,
demandando ai paragra successivi l'analisi delle scelte implementative.
3.3.1 JVMTI Agent
Questo componente è una
shared library
(.so, .dylib . . . ) caricata durante
la fase di avvio della JVM; I suoi compiti sono:
•
Registrare gli eventi sollevati dalla JVM implementando le funzioni di
callback JVMTI
•
Recuperare i dati circa lo stato della JVM attraverso le funzioni JVMTI
e BCI
•
Inviare i dati raccolti al Local MonitorDaemon
•
Salvare una copia dei dati in un le sul lesystem locale; il formato del
le è specicato in Tabella 3.1
L'instrumentazione dello Java ByteCode è eettuata per: rilevare i
text switch,
con-
la nalizzazione degli oggetti, la fase di inizializzazione degli
oggetti ed ottenere un elenco delle risorse occupate (i.e.
socket).
42
le decriptors,
Un'Architettura per il monitoraggio online
della Java Virtual Machine
3.3.2 Local Monitor Daemon
Questo componente è eseguito su un'altra Virtual Machine, non instrumentata.
E' incaricato di processare i dati, raccolti ed inviati dall'agenti
JVMTI, e di noticare eventuali fallimenti e cambiamenti importanti dello stato della JVM al Data Collector.
Failure Detector,
Nel dettaglio del compomente
VM
questo è delegato a riconoscere un fallimento della JVM
monitorata; svolte il suo compito rispetto a tre diversi livelli:
• Process Layer,
un crash della JVM è rilevato eettuando un controllo
sul suo PID
• Local Log Layer,
Hang failure (ad esempio un deadlock), sono rilevati
controllando il le di log locale e vericando che alcuni eventi siano
presenti o meno.
• Communication Layer, per vericare che la JVM monitorata sia ancora
in grado di comunicare, si resta in ascolto del messaggio di HeartBeat.
Questo controllo ci consente di stabilire che la JVM monitorata sia ancora in grado di comunicare, e che quindi il fallimento, eventualmente,
è avvenuto a livello del livello JMX.
3.3.3 Data Collector
Questo componente è delegato a raccogliere i dati dalle Virtual Machine
instrumentate; viene stabilita una connessione JMX con il Local Monitor
Event Listeners
Daemon.
Gli
mentre lo
Snapshot Retriever
tengono traccia dei cambiamenti di stato
raccoglie le istantanee degli stati.
raccolti sono registrati in due basi di dati, rispettivamente
e
State Snapshot Database
Event Database
attraverso una connessione JDBC.
43
I dati
Un'Architettura per il monitoraggio online
della Java Virtual Machine
3.4 Soluzioni di Design
Soermiamoci adesso ad analizzare le politiche adottate in fase di progetto
dei componenti dell'unità di monitoraggio, e sulle scelte di implementazione
proposte; passeremo in rassegna le funzionalità oerte dall'unità di monitoraggio e mostreremo come queste siano state implementate sfruttando il
background tecnico-tecnologico disponibile. Anzitutto, va ribadita la presenza di due
agenti
connessi alla Virtual Machine monitorata: un agente JVMTI
ed un agente scritto il linguaggio Java. L'agente JVMTI fornisce funzionalità
di monitoraggio dello stato della VM, mentre l'agente Java è incaricato di
provvedere all'instrumentazione dinamica delle classi. E' importante sottolineare che l'esecuzione contemporanea di più agenti avviene in modo del tutto
trasparente, nel senso che non perturbano la naturale esecuzione delle applicazioni Java, lo stato della VM; inoltre gli agenti JVMTI, non condividendo
gli environments JVMTI e JNI non sorono di mutue interazioni. Mostriamo adesso che eettivamente l'unità di monitoraggio non altera la naturale
evoluzione dello stato (vedi Figura
3.3 )della Virtual Machine, ossia:
44
Un'Architettura per il monitoraggio online
della Java Virtual Machine
Figura 3.3: Evoluzione dello Stato della Virtual Machine
Anchè l'unità di monitoraggio non alteri il
behaviour
della VM, devono
essere soddisfatte le seguenti condizioni:
1. Non deve essere alterata la sincronizzazione dei thread: il monitor non
interviene aatto nella sincronizzazione dei thread, si limita ad osservarne l'esecuzione ed a segnalare i cambiamenti nello stato degli
stessi.
2. Bisogna evitare di saturare l'area Heap: infatti i dati raccolti dal monitoraggio vengono allocati nell'area del processo monitor ed, in ogni
caso, le risorse in termini di memoria vengono liberate periodicamente.
3. Evitare di sollevare eccezioni: l'unità di monitoraggio
45
Un'Architettura per il monitoraggio online
della Java Virtual Machine
4. L'instrumentazione non deve alterare la semantica dei metodi: le modiche apportate al
body
dei metodi instrumentati, sono puramen-
te additive, non vengono aggiunti nuovi metodi alle classi, ne viene
introdotta alcuna perturbazione alle proprietà delle stesse.
Esaminiamo adesso le funzionalità oerte dai due agenti.
3.4.1 Descrizione dell' Agente JVMTI
La scrittura di un agente JVMTI è una operazione abbastanza schematica.
La libreria deve esportare una funzione di start-up con il seguente
prototipo:
JNIEXPORT jint JNICALL
Agent_OnLoad(JavaVM *vm, char *options, void *reserved)
Questa funzione verrà invocata dalla Virtual Machine non appena la libreria
viene caricata, appena prima della fase di inizializzazione della VM. Nell'intervallo di tempo che intercorre tra la
to dell'evento
VMInit
OnLoad Phase
e il sollevamen-
la libreria può, in sicurezza, impostare le proprie
caratteristiche:
Capabilities
•
Abilitare le
•
Abilitare la notica degli eventi
•
Impostare le
Callbacks
Passiamo in rassegna le features dell'agente JVMTI; l'agente è in grado di
intercettare una vasta serie di eventi, alcuni dei quali particolamente utili per
gli scopi da noi pressi, ad esempio:
•
Eventi relativi al ciclio di vita della VM:
46
Un'Architettura per il monitoraggio online
della Java Virtual Machine
1. VMStart: segnala l'avvio della VM, in questa fase la VM non è in
grado di eseguire Bytecode Java, ma l'ambiene JNI è gia in stato
live
e possono, quindi, essere invocati metodi nativi.
2. VMInit:
segnala il completamento della fase di inizializzazione
della VM, d'ora innanzi l'agente può invocare sia metodi JNI che
JVMTI.
3. VMDeath: segnala che la VM ha terminato la propria esecuzione.
•
2
ClassFileLoadHook :
viene inviato quando la VM ottiene i dati bi-
nari di un classle, ma prima che possa costruire una rapprentazione
macchina per quella classe. Questo evento viene sollevato anche come
reazione al metodo
RedefineClasses, ad esempio a seguito dell'instru-
mentazione di una classe.
•
Eventi relativi alla Garbage Collection:
1. GarbageCollectionStart: viene inviato quando inizia una collection completa. Soltanto l'avvio dei Serial Collectors (detti
stop-
the-world ) vengono riportati.
2. GarbageCollectionFinish: viene solletavo quando termina una collection; vale anche in questo caso la considerazione precendente.
•
Eventi relativi alla sincronizzazione delle risorse:
1. MonitorContendedEnter: mostra quando un
thread
tenta di acce-
attesa su un
dere ad un monitor gia acquisito da un altro thread(
mutex ).
2 in
realtà la segnalazione di questo evento non è indispensabile a scopo di monitoraggio,
viene citato in questa sede solo per mantenere organica l'esposizione delle funzionalità
fornite dall'API JVMTI
47
Un'Architettura per il monitoraggio online
della Java Virtual Machine
2. MonitorContendedWait: segnala quando un thread è in attesa di
attesa su una condition variable ).
un oggetto (
3. MonitorContendedEntered, MonitorContendedWaited: segnalano
rispettivamente quando un thread accede al monitor successiva-
nel pri-
mente ad una wait, e la ne dell'attesa per un oggetto(
mo caso, viene segnalato l'accesso al mutex, nel secondo, viene
ricevuto il segnale notify ).
L'interfaccia JVMTI fornisce un nutrito set di funzioni utili per la gestione dei thread, della memoria heap, delle classi, etc. L'analisi dell'area di
memoria Heap della Java Virtual Machine, viene condotta compiendo iterazioni dell'heap e applicando delle Tags agli oggetti presenti. Di particolare
interesse sono queste funzioni dell' API JVMTI:
• IterateOverHeap:
quelli
Reachable
• SetTag:
Itera su tutti gli oggetti presenti nell'area Heap, sia
sia quelli Unreachable.
Consente di etichettare un oggetto con una
un identicatore univoco (una
label ; l'etichetta è
jlong per compatibilità con le denizioni
JNI).
L'uso congiunto delle funzioni di questa famiglia consente di eettuare
3
una scansione
estremamente dettagliata degli oggetti presenti nella Heap
Memory, in particolare di capire quali di questi sono raggiungibili e quali no,
e di ricavare delle snapshots dello stato dello Heap in maniera sistematica.
Le informazioni circa i thread, poi, vengono raccolte grazie al set di funzioni disponibili nella API JVMTI; le informazioni di interesse sono essenzialmente circa lo stato dei thread,
3 Durante
l'esecuzione di queste funzioni lo stato dello Heap non cambia; non vengono
allocati ne deallocati oggetti, non avviene la Garbage Collection e, soprattutto, lo stato
degli oggetti non viene perturbato. Tipicamente le chiamate JNI sono messe in stallo.
48
Un'Architettura per il monitoraggio online
della Java Virtual Machine
• GetThreadInfo:
restituisce una struttura dati contenente, tra l'altro,
il nome del thread e la priorità.
• GetThreadState:
il valore di ritorno, in relazione ad una bit mask
denita nella specica JVMTI, di questa funzione, consente di ottenere
dettagliate informazioni sullo stato dei thread;
• GetOwnedMonitorInfo:
fornisce informazioni circa il monitor acquisito
dal thread
3.4.2 Instrumentation Tool, (Agente Java)
Al ne di intercettare eventi quali la creazione e la nalizzazione di oggetti, e di mostrare l'utilizzo da parte dell'applicazione, di risorse di sistema
quali
File Descriptors, Sockets,
si è dovuto ricorrere alle tecniche di Byte-
code Injection; nello specico, il monitor è in grado di instrumentare dinamicamente i costruttori di qualsiasi classe, anche di sistema (i.e.
appartenenti ai packages
ve derivare da
java.lang, java.io
java.lang.Object,
... ). Ogni oggetto Java de-
quindi eredita il metodo
eventualmente, questo viene sovrascritto(
le classi
finalize()
ed,
overriding ); per questo motivo, an-
che gli eventi di nalizzazione vengono intercettati tramite BCI. Intercettare
l'invocazione del metodo
finalize()
è importante perchè è questo metodo
a performare le operazioni atte a liberare le risorse utilizzate da un oggetto (non è il Garbage Collector ad eettuare tale operazione). In denitiva,
per realizzare il monitoraggio degli eventi di allocazione / nalizzazione di
oggetti ed il monitoraggio delle risorse in uso (restringendo il campo ad i
File ed alle Socket in uso),è stata sfuttata l'interfaccia di Instrumentazione
java.lang.Instrument
messa a disposizione dall'API del linguaggio.
agente, sviluppato con questa tecnica, deve implementare il metodo
49
Un
Un'Architettura per il monitoraggio online
della Java Virtual Machine
public static void premain(String agentArgs, Instrumentation inst);
che verrà invocato non appena l'inizializzazione della Java Virtual Machine è completata. Di un agente Java viene eettuato il deploy sotto forma di
Jar-Archive.
La classe, dell'agente di instrumentazione, contenente il metodo
premain deve implementare l'interfaccia java.lang.instrument.ClassFileTransformer,
e quindi il metodo:
50
Un'Architettura per il monitoraggio online
della Java Virtual Machine
byte[] transform(ClassLoader loader,
String className,
Class<?> classBeingRedefined,
ProtectionDomain protectionDomain,
byte[] classfileBuffer)
throws IllegalClassFormatException
Questo metodo, in denitiva, svolge la funzione omologa del metodo
RedefineClasses() incontrato nell'API JVMTI. L'interfaccia di instrumentazione non fornisce nessuno strumento integrato per eettuare Bytecode Injection; l'agente infatti, è stato sviluppatto sfruttando congiuntamente l'interfaccia di instrumentazione e due librerie speciche per eettuare bytecode
injection:
1. BCEL: Byte Code Engineering Library
2. javassist
Il problema dell'instrumentazione, è abbastanza complesso, nel senso che
presenta diversi vincoli e necessita di seguire alcune strategie per raggiungere
determinati obiettivi; innanzitutto, l'instrumentazione dei costruttori (metodi con signature
.<init>)
non deve essere eettuata per ogni oggetto della
Virtual Machine. Uno dei problemi, che penalizza fortemente le prestazioni,
dei tools di monitoraggio / proling disponibili a tutt'oggi è l'impatto che
hanno sulle applicazioni in esecuzione; JVMMon infatti, provvede ad una instrumentazione selettiva degli oggetti, andando a selezionare solo quelli eettivamente di rilievo per il monitoraggio. Nonostante siano disponibili eventi
JVMTI che segnalano l'inizio e la ne della garbage collection (ricordiamo
solo per serial collectors), per ottenere una informazione precisa circa la nalizzazione degli eventi è necessario sapere quando i metodi
51
finalize()
Un'Architettura per il monitoraggio online
della Java Virtual Machine
vengono invocati.
Per tale ragione si è proceduto ad instrumentare anche
4
questi ultimi .
L'instrumentazione delle classi si sistema, ed in particolare quelle che
si occupano di gestire l'I/O è più complessa; La Java Virtual Machine, in
uno stato di normale funzionamento, non consente di modicare a runtime
un oggetto, ma abilitato il supporto a JPDA (Java Platform Debugger Architecture), è possibile, tramite
HotSwapping
rarggiungere questo obiettivo.
Figura 3.4: Object Hot Swapping
L'utilizzo della tecnica dell'Hot Swapping (vedi Figura
3.4) è necessaria
perchè l'agente Java viene caricato dopo che è terminata la fase di inizializzazione della Java Virtual Machine, ovvero quando gia circa 800 classi sono
state caricate, tra queste ci sono alcune classi di
libreria javassist fornisce la classe
java.io
e
java.net.
javassist.util.HotSwapper
La
che rende
disponibile tale funzionalità.
4 Nota
che il sollevamento degli eventi JVMTI GarbageCollectionStart, Finish potrebbe non coincidere con la nalizzazione degli oggetti. E' importante ricordare che
l'utility java_crw_demo non è in grado di instrumentare i metodi nalize().
52
Un'Architettura per il monitoraggio online
della Java Virtual Machine
3.4.3 Communication Layer
Data la complessità dell' architettura di JVMMon, è stato necessario
ponderare bene il livello comunicazione tra i diversi agenti ed il resto del
sistema. Per ciò che riguarda gli agenti JVMTI, essenzialmente sono state
individuate tre strategie di comunicazione:
1. Comunicazione su Filesystem
2. Comunicazione tramite la creazione di una area di memoria condivisa
(shared memory) su cui operino i due agenti JVMTI descritti precedentemente. In questo caso la comunicazione avviene secondo le regole
tipiche note.
3. Comunicazione tramite Socket: i due agenti JVMTI, comunicano tra
loro secondo un paradigma client-server attraverso delle socket conformi
allo standard
Berkeley.
4. In fase di sviluppo di JVMMon è stata proposta una ulteriore possibilità; ossia che gli agenti, tramite chiamate native, possano scrivere
direttamene i dati estratti nel corso del monitoraggio in una base di
dati, evitando così di dover eettuare costose chiamate JDBC e limitando così l'impatto sulle performance e, soprattutto, senza rischiare di
alternare il normale decorso dell'applicazione e della Virtual Machine
monitorata.
53
Un'Architettura per il monitoraggio online
della Java Virtual Machine
3.5 Valutazione Sperimentale
Questa sezione descrive i risultati dei performance test condotti sull'architettura JVMMon. I test sono stati eettuati eseguento la suite di benchmarks
SPEC JVM98.
Alla ne l'overhead do JVMMon è stato messo a paragone
con i risultati dei test condotti con HPROF.
3.5.1 Obiettivo delle Misure
L'obiettivo delle misure è quello di ottenere un riferimento in termini di
impatto sulle prestazione della Virtual Machine instrumentata; ricordiamo
infatti che nella specica dell'architettura esiste un vincolo per il quale l'unità
di monitoraggio deve essere quanto meno invasiva possibile.
3.5.2 Testbed Utilizzato
La piattaforma su cui sono stati eseguiti i test è di seguito descritta: single
Pentium 4 Processor (3.40 GHz), 2 Gb di memoria RAM, sistema operativo
Linux (kernel 2.6.8, NPTL 0.40).
La Java Virtual Machine utilizzata è la
Sun HotSpot 1.5.0_01 Le applicazione che fungevano da workload spaziavano
ampiamente in svariati campi di utilizzazione, in particolare:
• _201_compass:
• _202_jess:
• _209_db:
è un tool di compressione dati tipo CPU-intensive
è una console che esegue una serie di puzzle game
eettua delle query su di una base di dati memory resident
• _213_javac:
eettua svariate compilazioni di codice sorgente Java
• _222_mpegaudio:
eettua la decodica di un le audio codicato in
MPEG Layer-3 (MP3)
54
Un'Architettura per il monitoraggio online
della Java Virtual Machine
• _227_mtrt:
è l'unico test multithread disponibile nella suite; il bench-
mark consiste di un ray-tracer che disegna un dinosauro.
• _228_jack:
è un parser generator.
• ProdCons:
è un semplice benchmark del tipo Produttore / Consu-
matore, creato per testare il comportamento del monitor su di una
applicazione multithreading spinta.
Ogni test è stato eseguito 100 volte: 50 volte con la Virtual Machine in
congurazione
client,
altrettante in modalità
server.
Alcuni campioni sono
stati rimossi per eliminare le uttuazioni dovute a fenomeni di cashing, oppure alla presenza degli altri processi di sistema in esecuzione sulla macchina
test.
3.5.3 Risultati
Vengono stimati sia i
time overhead
che la
memory footprint ; Non è sta-
to identicato un marcato impatto dell'architetture JVMMon sull'area Heap
della Virtual Machine Instrumentata: la memory footprint delle applicazioni
test eseguite sulla VM instrumentata crescono con un indice pari a circa il 3%.
L'osservazione puntuale del memory footprint è stata condotta analizzando
la memoria allocata prima di ogni garbage collection imponendo l'opzione
-verbosegc al Virtual Machine Launcher.
Analizzando i risultati degli espe-
rimenti il dato rilevante osservato è stato il peggioramento delle performance
del sistema a seguito di ripetute e durature iterazioni dell'heap. Il logging
degli eventi invece, non è fonte di overhead.
Nell'attuale implementazione
l'iterazione dell'Heap è eseguita in sincro con ne della Garbage Collection.
Sono stati identicati due fattori principali che aiggono JVMMon:
1. Il numero di oggetti presenti nell'area Heap
2. la frequenza delle Collections
55
Un'Architettura per il monitoraggio online
della Java Virtual Machine
Figura 3.5: Overhead introdotto da JVMmon
Figura 3.6: Performance Overhead, comparativa tra JVMMon ed HPROF
56
Un'Architettura per il monitoraggio online
della Java Virtual Machine
In Figura
3.6 viene presentata una prova comparativa tra JVMMon ed
HPROF; le seguenti features di HPROF sono state abilitate: Heap Allocation
Sites Monitring, Monitor Contentions Monitoring, Garbage Collection Monitoring. Le prove sperimentali mostrano come HPROF sia particolarmente
più invasivo di JVMMon e di come quindi, sia inadatto ad essere utilizzato
come unità di monitoraggio di una JVM.
3.6 Conclusioni
Abbiamo avuto modo di analizzare la complessa architettura della Java
Virtual Machine; un sistema che ha richiesto, per la sua implementazione, oltre un milione di linee di codice. Risulta necessario per un sistema software, di
tale complessità, testarne l'adabilità in contesti di utilizzo
mission critical.
Una prima analisi condotta ci ha permesso di identicare i componenti architetturali più critici in relazione allo studio di adabilità; successivamente è
stato necessario identicare la metodologia di analisi che meglio si adattava
al contesto della Java Virtual Machine. E' stata evidenziata l'importanza di
possedere una unità di monitoraggio pensata
bility Analysis ;
ad hoc
per eettuare
una unità che sia in grado di catturare delle
Dependa-
snapshots
dello
stato della JVM. Sono state introdotte alcune tecniche e tecnologie impiegate nell'implementazione di JVMMon: la tecnica della
Bytecode Injection
e le
tecnologie JVMTI e JMX. E' stata presentata l'architettura ed alcune delle
scelte di design di JVMMon (Java Virtual Machine Monitoring System); il
sistema di monitoraggio poi, è stato impiegato in una serie di test per valutarne le performance ed il livello di invasività nei confronti dell'applicazione
monitorata.
I test condotti hanno mostrato che l'overhead, introdotto dal
sistema di monitoraggio, in termini di occupazione di memoria (
print )
è, mediamente, nell'ordine del 3 %; Futuri
branchworks
memory footdi JVMMon
dovranno tendere a migliorare le operazioni di iterazione dell'heap memory,
57
Un'Architettura per il monitoraggio online
della Java Virtual Machine
e la comunicazione tra gli agenti JVMTI, in modo da ottenere una sensibile
riduzione del
time overhead
introdotto dall'unità di monitoraggio.
58
Un'Architettura per il monitoraggio online
della Java Virtual Machine
Figura 3.7: Memory Footprint Comparison
59
Appendice A
Di seguito viene proposto un esempio di agente JVMTI:
Listing A.1: Agente JVMTI
#include <stdio.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "denitions .h"
#include "color.h"
static GlobalData globalData, ∗gdata = &globalData;
jobject makeGlobalReference(JNIEnv∗ jni);
/∗ Check for NULL pointer error ∗/
#dene CHECK_FOR_NULL(ptr) \
checkForNull(ptr, __FILE__, __LINE__)
static void
checkForNull(void ∗ptr, char ∗ le , int line )
{
if ( ptr == NULL ) {
fprintf (stderr , "ERROR: NULL pointer error in %s:%d\n", le, line);
abort();
60
Un'Architettura per il monitoraggio online
della Java Virtual Machine
}
}
/∗ Deallocate JVMTI memory ∗/
static void
deallocate(jvmtiEnv ∗jvmti, void ∗p)
{
jvmtiError err ;
err = (∗jvmti)−>Deallocate(jvmti, (unsigned char ∗)p);
if ( err != JVMTI_ERROR_NONE ) {
fprintf (stderr , "ERROR: JVMTI Deallocate error err=%d\n", err);
abort();
}
}
/∗ Get name for JVMTI error code ∗/
static char ∗
getErrorName(jvmtiEnv ∗jvmti, jvmtiError errnum)
{
jvmtiError err ;
char
∗name;
err = (∗jvmti)−>GetErrorName(jvmti, errnum, &name);
if ( err != JVMTI_ERROR_NONE ) {
fprintf (stderr , "ERROR: JVMTI GetErrorName error err=%d\n", err);
abort();
}
return name;
}
/∗ Check for JVMTI error ∗/
#dene CHECK_JVMTI_ERROR(jvmti, err) \
checkJvmtiError(jvmti, err, __FILE__, __LINE__)
static void
61
Un'Architettura per il monitoraggio online
della Java Virtual Machine
checkJvmtiError(jvmtiEnv ∗jvmti, jvmtiError err, char ∗ le , int line )
{
if ( err != JVMTI_ERROR_NONE ) {
char ∗name;
name = getErrorName(jvmti, err);
fprintf (stderr , "ERROR: JVMTI error err=%d(%s) in %s:%d\n",
err , name, le , line );
deallocate(jvmti, name);
abort();
}
}
/∗ Enter agent monitor protected section ∗/
static void
enterAgentMonitor(jvmtiEnv ∗jvmti)
{
CHECK_JVMTI_ERROR(jvmti, (∗jvmti)−>RawMonitorEnter(jvmti, gdata−>lock));
}
/∗ Exit agent monitor protected section ∗/
static void
exitAgentMonitor(jvmtiEnv ∗jvmti)
{
CHECK_JVMTI_ERROR(jvmti, (∗jvmti)−>RawMonitorExit(jvmti, gdata−>lock));
}
/∗Callbacks Denitions ∗/
static void JNICALL vmStart(jvmtiEnv∗ jvmti, JNIEnv∗ jni)
{
clock_t timestamp_vmStart = clock();
clock_t millisec ;
millisec = ( oat )( timestamp_vmStart − gdata−>agent_clock ) /
( ( oat )CLOCKS_PER_SEC / 1000 );
62
Un'Architettura per il monitoraggio online
della Java Virtual Machine
fprintf (stdout, "%s", GREEN);
fprintf (stdout, "VM STARTED @ : %d milliseconds \n", millisec);
fprintf (stdout, "%s", RESET);
}
static void JNICALL vmInit(jvmtiEnv∗ jvmti, JNIEnv∗ jni, jthread thread)
{
gdata −> VMInit = 1;
clock_t timestamp_vmInit = clock();
clock_t millisec ;
millisec = ( oat )( timestamp_vmInit − gdata−>agent_clock ) /
( ( oat )CLOCKS_PER_SEC / 1000 );
fprintf (stdout, "%s", BLUE);
fprintf (stdout, "VM INITIALIZED @ : %d milliseconds \n ", millisec);
fprintf (stdout, "%s", RESET);
}
static void JNICALL vmDeath(jvmtiEnv∗ jvmti, JNIEnv∗ jni)
{
clock_t timestamp_vmDeath = clock();
clock_t millisec ;
millisec = ( oat )( timestamp_vmDeath − gdata−>agent_clock ) /
( ( oat )CLOCKS_PER_SEC / 1000 );
fprintf (stdout, "%s", RED);
fprintf (stdout, "VM DEATH @ : %d milliseconds \n", millisec);
fprintf (stdout, "%s", RESET);
}
static void JNICALL classFileLoadHook(jvmtiEnv∗ jvmti, JNIEnv∗ jni,
jclass class , jobject loader,
const char∗ name, jobject protection_domain,
jint class_data_len, const unsigned char∗ class_data,
jint ∗ new_class_data_len,
unsigned char∗∗ new_class_data)
{
clock_t timestamp_classFileLoadHook = clock();
enterAgentMonitor(jvmti);
63
Un'Architettura per il monitoraggio online
della Java Virtual Machine
fprintf (stdout, "HOOKED : %s\n", name );
exitAgentMonitor(jvmti);
if (gdata −> VMInit == 1){
/∗
∗ La VM ? in pausa; la class class attraverso una chiamata nativa
∗ all 'oggetto gdata −> instrumenterObject viene instrumentata e, poi,
∗ passata alla VM;
∗ La VM esce dallo stato di pausa!
∗/
if (gdata −> instrumenterObject != NULL) {
jclass cls = (∗jni) −> GetObjectClass(jni, gdata −> instrumenterObject);
jmethodID mid =
(∗ jni ) −> GetMethodID(jni, cls, "INSTRUMENTATORE","()V");
if (mid != NULL) {
return;
}
(∗ jni ) −> CallVoidMethod(jni,
gdata −> instrumenterObject,
mid);
}
}
}
static void JNICALL classPrepare(jvmtiEnv ∗jvmti, JNIEnv∗ jni,
jthread thread, jclass klass)
{
jvmtiError
error ;
char∗
signature_ptr;
char∗
generic_ptr;
enterAgentMonitor(jvmti);
error = (∗jvmti) −> GetClassSignature (jvmti, klass, &signature_ptr, &generic_ptr);
64
Un'Architettura per il monitoraggio online
della Java Virtual Machine
/∗ se non ? stato mai intercettato un SimpleAgent ∗/
if (gdata −> found == 0){
if (strcmp(signature_ptr, "LSimpleAgent;") == 0){
gdata −> found = 1;
fprintf (stdout, "SIMPLE_AGENT FOUND \n");
}
}
/∗? gia stato intercettato un simple agent∗/
if (gdata −> found == 1){
if (strcmp(signature_ptr, "LInstrumenter;") == 0){
fprintf (stdout, "INTRUMENTER FOUND \n");
// Instanzio Nativamente un oggetto Instrumenter
gdata −> instrumenterObject = makeGlobalReference(jni);
}
}
exitAgentMonitor(jvmti);
}
JNIEXPORT jint Agent_OnLoad(JavaVM∗ vm, char∗ options, void∗ reserved)
{
jint
rc ;
jvmtiError
error ;
jvmtiCapabilities
capabilities ;
jvmtiEventCallbacks
callbacks ;
jvmtiEnv∗
jvmti;
/∗Get jvmti Environment∗/
jvmti = NULL;
gdata −> found = 0;
gdata −> VMInit = 0;
rc = (∗vm) −> GetEnv(vm, (void∗∗)&jvmti, JVMTI_VERSION);
if (rc != JNI_OK) {
65
Un'Architettura per il monitoraggio online
della Java Virtual Machine
fprintf (stderr , "ERROR : Unable to create jvmtiEnv, GetEnv Failed, error=%d\n", rc);
return −1;
}
CHECK_FOR_NULL(jvmti);
/∗Get/Add Capabilities∗/
error = (∗jvmti) −> GetCapabilities(jvmti, &capabilities);
CHECK_JVMTI_ERROR(jvmti, error);
capabilities .can_generate_all_class_hook_events = 1;
error = (∗jvmti) −>AddCapabilities(jvmti, &capabilities);
CHECK_JVMTI_ERROR(jvmti, error);
/∗Create a RAW Monitor∗/
error = (∗jvmti) −> CreateRawMonitor(jvmti, "Agent Lock", &(gdata−>lock));
CHECK_JVMTI_ERROR(jvmti, error);
/∗Set Callbacks and Enable Event Notications∗/
memset (&callbacks, 0 , sizeof (callbacks ));
callbacks .VMStart
=&vmStart;
callbacks .VMInit
=&vmInit;
callbacks .ClassFileLoadHook =&classFileLoadHook;
callbacks .VMDeath
=&vmDeath;
callbacks .ClassPrepare
=&classPrepare;
error = (∗jvmti) −> SetEventCallbacks(jvmti, &callbacks, sizeof(callbacks));
CHECK_JVMTI_ERROR(jvmti, error);
error = (∗jvmti) −> SetEventNoticationMode(jvmti, JVMTI_ENABLE,
JVMTI_EVENT_VM_START, NULL);
error = (∗jvmti) −> SetEventNoticationMode(jvmti, JVMTI_ENABLE,
JVMTI_EVENT_VM_INIT, NULL);
66
Un'Architettura per il monitoraggio online
della Java Virtual Machine
error = (∗jvmti) −> SetEventNoticationMode(jvmti, JVMTI_ENABLE,
JVMTI_EVENT_CLASS_FILE_LOAD_HOOK, NULL);
error = (∗jvmti) −> SetEventNoticationMode(jvmti, JVMTI_ENABLE,
JVMTI_EVENT_VM_DEATH, NULL);
error = (∗jvmti) −> SetEventNoticationMode(jvmti, JVMTI_ENABLE,
JVMTI_EVENT_CLASS_PREPARE, NULL);
CHECK_JVMTI_ERROR(jvmti, error);
return 0;
}
/∗
∗ Una funzione che crea una GlobalReference all'instrumentatore JAVA
∗ attraverso una chiamata JNI
∗/
jobject makeGlobalReference(JNIEnv∗ jni)
{
jclass
klass ;
jmethodID
cid ;
jobject
result ;
klass = (∗jni) −> FindClass(jni, "Instrumenter");
if (klass == NULL) {
return NULL;
}
cid = (∗jni) −> GetMethodID(jni, klass, "<init>", "()V");
if (cid == NULL) {
return NULL;
}
result = (∗jni) −> NewObject(jni, klass, cid);
(∗ jni ) −> DeleteLocalRef(jni, klass);
67
Un'Architettura per il monitoraggio online
della Java Virtual Machine
return result ;
}
JNIEXPORT void JNICALL Agent_OnUnload(JavaVM∗ vm)
{
}
68
Un'Architettura per il monitoraggio online
della Java Virtual Machine
Listing A.2:
Esempio di Instrumentazione di una Classe Java attraverso
BCEL
import java.io .∗;
import org.apache.bcel. classle .∗;
import org.apache.bcel.generic .∗;
import org.apache.bcel.Constants;
public class BCELTiming
{
/∗∗
∗ Add timing wrapper to method of class. The method can accept any
∗ arguments and return any type (including void), but must be a normal
∗ (non−static, non− initializer ) method to be used with this code as
∗ currently implemented. Handling the other types of methods would not
∗ involve any fundamental changes to the code.
∗
∗ @param cgen generator for class being modied
∗ @param method current method to be enhanced with timing wrapper
∗/
private static void addWrapper(ClassGen cgen, Method method) {
// set up the construction tools
InstructionFactory ifact = new InstructionFactory(cgen);
InstructionList ilist = new InstructionList();
ConstantPoolGen pgen = cgen.getConstantPool();
String cname = cgen.getClassName();
MethodGen wrapgen = new MethodGen(method, cname, pgen);
wrapgen.setInstructionList( ilist );
// rename a copy of the original method
MethodGen methgen = new MethodGen(method, cname, pgen);
cgen.removeMethod(method);
69
Un'Architettura per il monitoraggio online
della Java Virtual Machine
String iname = methgen.getName() + "$impl";
methgen.setName(iname);
cgen.addMethod(methgen.getMethod());
// compute the size of the calling parameters
Type[] types = methgen.getArgumentTypes();
int slot = methgen.isStatic() ? 0 : 1;
for (int i = 0; i < types.length; i++) {
slot += types[i].getSize ();
}
// save time prior to invocation
ilist .append(ifact.createInvoke("java.lang.System",
"currentTimeMillis", Type.LONG, Type.NO_ARGS,
Constants.INVOKESTATIC));
ilist .append(InstructionFactory.createStore(Type.LONG, slot));
// call the wrapped method
int oset = 0;
short invoke = Constants.INVOKESTATIC;
if (! methgen.isStatic()) {
ilist .append(InstructionFactory.createLoad(Type.OBJECT, 0));
oset = 1;
invoke = Constants.INVOKEVIRTUAL;
}
for (int i = 0; i < types.length; i++) {
Type type = types[i];
ilist .append(InstructionFactory.createLoad(type, oset ));
oset += type.getSize();
}
Type result = methgen.getReturnType();
ilist .append(ifact.createInvoke(cname,
iname, result , types, invoke ));
// store result for return later
70
Un'Architettura per il monitoraggio online
della Java Virtual Machine
if ( result != Type.VOID) {
ilist .append(InstructionFactory.createStore(result , slot +2));
}
// print time required for method call
ilist .append(ifact.createFieldAccess("java.lang.System", "out",
new ObjectType("java.io.PrintStream"), Constants.GETSTATIC));
ilist .append(InstructionConstants.DUP);
ilist .append(InstructionConstants.DUP);
String text = "Call to method " + methgen.getName() + " took ";
ilist .append(new PUSH(pgen, text));
ilist .append(ifact.createInvoke("java. io .PrintStream", "print",
Type.VOID, new Type[] { Type.STRING }, Constants.INVOKEVIRTUAL));
ilist .append(ifact.createInvoke("java.lang.System",
"currentTimeMillis", Type.LONG, Type.NO_ARGS,
Constants.INVOKESTATIC));
ilist .append(InstructionFactory.createLoad(Type.LONG, slot));
ilist .append(InstructionConstants.LSUB);
ilist .append(ifact.createInvoke("java. io .PrintStream", "print",
Type.VOID, new Type[] { Type.LONG }, Constants.INVOKEVIRTUAL));
ilist .append(new PUSH(pgen, " ms."));
ilist .append(ifact.createInvoke("java. io .PrintStream", "println",
Type.VOID, new Type[] { Type.STRING }, Constants.INVOKEVIRTUAL));
// return result from wrapped method call
if ( result != Type.VOID) {
ilist .append(InstructionFactory.createLoad(result, slot +2));
}
ilist .append(InstructionFactory.createReturn(result));
// nalize the constructed method
wrapgen.stripAttributes(true);
wrapgen.setMaxStack();
wrapgen.setMaxLocals();
cgen.addMethod(wrapgen.getMethod());
71
Un'Architettura per il monitoraggio online
della Java Virtual Machine
ilist .dispose ();
}
public static void main(String[] argv) {
if (argv.length == 2 && argv[0].endsWith(".class")) {
try {
JavaClass jclas = new ClassParser(argv[0]).parse();
ClassGen cgen = new ClassGen(jclas);
Method[] methods = jclas.getMethods();
int index;
for (index = 0; index < methods.length; index++) {
if (methods[index].getName().equals(argv[1])) {
break;
}
}
if (index < methods.length) {
addWrapper(cgen, methods[index]);
FileOutputStream fos = new FileOutputStream(argv[0]);
cgen.getJavaClass().dump(fos);
fos . close ();
} else {
System.err.println ("Method " + argv[1] + " not found in " +
argv [0]);
}
} catch (IOException ex) {
ex.printStackTrace(System.err);
}
} else {
System.out.println("Usage: BCELTiming class−le method−name");
}
}
}
72
Bibliograa
[1]
Analysis of Software Aging in a Web Server, volume IEEE Transactions
on Reliability Vol. 55, NO 3, 2006.
[2] Arup Mukherjee Daniel P. Siewiorek.
Measuring software dependa-
bility by robustness benchmarking.
IEEE TRANSACTIONS ON
In
SOFTWARE ENGINEERING, volume 23, pages 366 378, 1997.
[3] Fifth IEEE International Symposium on Network Computing and Applications (NCA'06).
Software Aging and Rejuvenation in a SOAP-based
Server, 2006.
[4] Jim Gray.
Why do computers stop and what can be done about it ?
Technical report, Tandem Computers, 1985.
[5] IEEE, editor.
Failure Classication and analysis of the Java Virtual
Machine. International Conference on Distribuited Computing Systems,
2006.
[6] R. Chillarge J. Christmansson.
Generation of an error set that emu-
lates software faults based on eld data.
The Twenty-Sixth Annual
International Symposium on Fault-Tolerant Computing, 1996.
[7] Jean Arlat Karama Kanoun, Henrique Madeira.
dependability benchmarking.
In
DSN Workshop on Dependability
Benchmarking, volume F, pages 78. DSN, 2005.
73
A framework for
Un'Architettura per il monitoraggio online
della Java Virtual Machine
[8] Cli Click Michael Paleczny, Christopher Vick. The java hotspot server compiler.
In
Java
TM
Virtual Machine Research and Technology
Symposium (JVM '01). Usenix, 2001.
[9] Sun Microsystems. The java hotspot virtual machine, v1,4,0.
[10] Sun Microsystems. Tuning garbage collection with the 5.0 java virtual
machine. Technical report, Sun Microsystems, 2005.
[11] Je Hein Yuanbo Guo, Zhengxiang Pan.
Lubm:
A benchmark for
owl knowledge base systems. Technical report, Computer Science and
Engineering Department Lehigh University, 2005.
74