Questa DLL (Dynamic Linked Library) è stata scritta in C++, e svolge una duplice funzione:
La figura mostra chiaramente come il Profiler si serva degli eventi definiti in JVMPI per costruire il proprio database interno. Questo può essere consultato in qualsiasi momento dal monitor mediante funzioni native, cioè che sfruttano JNI.
Casualmente, o per fortuna, sia JNI che JVMPI usano il meccanismo delle DLL. Il meccanismo per caricarle è differente nei due casi e verrà descritto in seguito.
Che cos'è JNI? JNI sta per Java Native Interface, e permette di chiamare funzioni scritte in C o C++ come se fossero dei normali metodi Java. I vantaggi sono ovviamente le maggiori potenzialità ed efficienza offerte dal C, per contro il codice così scritto non è più portabile. Una guida a JNI è disponibile presso java.sun.com/products/jdk/faq/jni-j2sdk-faq.html. Si spera comunque che il codice di seguito esposto possa servire come esempio autoesplicante per capire i meccanismi fondamentali di questa interfaccia.
Che cos'è JVMPI? JVMPI sta per JVM Profiler Interface, e definisce un set di 36 eventi che la JVM può inviare ad un Profiler, cioè un modulo di codice contenuto in una DLL. Come gestire i dati comunicati da tali eventi è lasciato ovviamente alla fantasia del programmatore. Attualmente l'unica guida a JVMPI è disponibile presso java.sun.com/products/jdk/1.2/docs/guide/jvmpi, e si consiglia di tenerla sottomano.
L'inizializzazione è forse la fase più delicata e merita pertanto particolare attenzione.
Se si lancia Java con il comando java -Xrunjvmmonitor NomeClasse
la JVM caricherà automaticamente la DLL indicata da -Xrun. In particolare verrà subito chiamata la funzione JVM_OnLoad, che quindi rappresenta l'entry-point della DLL.
Vediamo, semplificandolo, il codice usato:
Il file jvmpi.h lo trovate in "jdk_dir"/include. Se non c'è probabilmente non avete installato tutto.
//file jvmmon.cpp
#include < jvmpi.h >
extern "C"{
// var statiche globali
static JVMPI_Interface *jvmpi;
//entry point al caricamento della dll
JNIEXPORT jint JNICALL
JVM_OnLoad(JavaVM *jvm, char *options, void *reserved)
{
//inizializzazione
jvm->GetEnv((void**)&jvmpi,JVMPI_VERSION_1);
//aggancio alla funzione di gestione degli eventi
jvmpi->NotifyEvent=notifyEvent;
//abilitazione a ricevere gli eventi indicati
jvmpi->EnableEvent(JVMPI_EVENT_CLASS_LOAD,NULL);
jvmpi->EnableEvent(JVMPI_EVENT_JVM_INIT_DONE,NULL);
jvmpi->EnableEvent(JVMPI_EVENT_JVM_SHUT_DOWN,NULL);
return JNI_OK;
}
}
La direttiva extern "C"
serve al compilatore e al linker del C++. In questo modo le tavole dei simboli generate seguono le specifiche del C anziche del C++, e ciò è richiesto da JVMPI. Omettendo questa istruzione l'entry-point non verrà trovato.
La variabile jvmpi è il cuore del Profiler. Essa è un puntatore ad una struttura dati composta per lo più da puntatori a funzioni. Quindi, tutte le istruzioni jvmpi->NomeFunzione(argomenti) sono in realtà delle chiamate a funzioni predefinite.
L'unica eccezione è costituita da jvmpi->NotifyEvent: questo puntatore va infatti collegato ad una funzione definita dall'utente. Essa sarà la funzione che gestirà gli eventi provenienti dalla JVM: sarà invocata automaticamente al verificarsi di ogni evento di interesse, e riceverà come argomento una struttura dati indicante per l'appunto il tipo di evento e altre sue caratteristiche.
Da questo momento in poi, insomma, il funzionamento della DLL è completamente asincrono: quando un thread Java eseguirà un'azione che determina un evento, la JVM devierà la sua esecuzione sulla funzione jvmpi->NotifyEvent. Il thread eseguirà il codice di tale funzione e, una volta terminato, riprenderà la sua esecuzione nel punto in cui si era interrotto. E' quindi più che mai naturale che più thread Java abbiano contemporaneamente il proprio program-counter all'interno della funzione jvmpi->NotifyEvent.
Si ricorda che inizialmente tutti i tipi di eventi sono disabilitati, e bisogna perciò dire alla JVM quali si desidera ricevere, utilizzando la funzione jvmpi->EnableEvent(flag).