In profondità

La funzione di ricezione degli eventi

La funzione di ricezione degli eventi è completamente scritta dal programmatore, e permette al Profiler di gestire gli eventi generati dalla JVM. Come già detto, il Profiler non ha un ben definito flusso di istruzioni (non è un thread nè un processo), in quanto sono i thread Java stessi ad eseguire, di volta in volta, il codice di questa funzione.
Vediamo ad esempio un frammento di codice:

  //funzione di ricezione degli eventi
  void notifyEvent(JVMPI_Event *ev)
  {
  //identifico il tipo di evento
  //dall'argomento passato
  switch(ev->event_type)
  {
  case JVMPI_EVENT_JVM_INIT_DONE:
    //abilitazione di altri eventi
    jvmpi->EnableEvent(JVMPI_EVENT_THREAD_START,NULL);
    jvmpi->EnableEvent(JVMPI_EVENT_THREAD_END,NULL);
    jvmpi->EnableEvent(JVMPI_EVENT_METHOD_ENTRY2,NULL);
    jvmpi->EnableEvent(JVMPI_EVENT_OBJECT_ALLOC,NULL);
    jvmpi->EnableEvent(JVMPI_EVENT_MONITOR_CONTENDED_ENTER,NULL);
    break;
  case JVMPI_EVENT_THREAD_START:
    //nasce un nuovo thread
    Stat stat;
    stat.method_num=0;
    stat.tcp_in=0;
    ...
    stat.monitor_num=0;
    lock();
    stat.thread=getJavaCurrentThread(ev->env_id);
    //aggiungo le sue statistiche in una tabella
    thread_table.add(ev->env_id,stat);
    unlock();
    break;
  case JVMPI_EVENT_THREAD_END:
    //termina un thread
    lock();
    thread_table.remove(ev->env_id);
    unlock();
    break;
    ...
Niente paura e analizziamo tutto con calma. La struttura di tipo JVMPI_Event che viene passata come argomento ha diversi campi. I più importanti sono env_id, un puntatore ad una struttura JNIEnv che indica il thread che ha generato l'evento, e env_type, una costante che indica il tipo di evento. I restanti campi variano a seconda del tipo di evento, e comunicano ulteriori informazioni di interesse. Per accedervi si utilizza pertanto una union.

L'evento di tipo JVMPI_EVENT_JVM_INIT_DONE si verifica appena la JVM ha finito la sua inizializzazione; si è aspettato questo evento prima di abilitarne altri come, ad esempio, JVMPI_EVENT_THREAD_START, perchè lo stato della JVM può essere, in questa fase, inconsistente: nascono dei thread di sistema (ad esempio il Garbage Collector), e vengono caricate delle classi basilari (ad esempio Thread e String). Tanto per capirsi, il primo thread che nascerà dopo JVMPI_EVENT_JVM_INIT_DONE sarà il main.
[L'evento JVMPI_EVENT_CLASS_LOAD era stato invece abilitato subito in JVM_OnLoad, in quanto, come si vedrà in seguito, si è interessati a "catturare" i riferimenti ad alcune classi basilari].

L'evento di tipo JVMPI_EVENT_THREAD_START indica che un nuovo thread Java sta iniziando la sua esecuzione (ciò non coincide con istanziare un oggetto java.lang.Thread, quanto piuttosto a chiamare il suo metodo start( )).
Viene creato un oggetto (C++) stat di tipo Stat, che non contiene altro che le statistiche per il nuovo thread. Una volta inizializzato, stat viene inserito nella tabella globale thread_table utilizzando il metodo add (così come, nell'evento JVMPI_EVENT_THREAD_END, viene tolto utilizzando thread_table.remove).
Le funzioni lock e unlock servono per garantire che un solo thread possa avere accesso a quella sezione di codice. Ciò è dettato dal fatto che, dovendo gestire una struttura dati comune -la tabella thread_table, appunto-, i thread devono accedervi in modo atomico, per mantenere sempre uno stato consistente dei dati.

La funzione getJavaCurrentThread merita qualche parola, poichè è l'idea "in più" di questo monitor rispetto a quelli presentati negli esempi della guida a JVMPI. Essa ricava il riferimento all'oggetto Java di tipo java.lang.Thread che corrisponde al flusso di esecuzioni corrente. Il codice è il seguente:


  jobject getJavaCurrentThread(JNIEnv *env)
   {
   jclass clazz;
   jmethodID mid;
   //trovo la classe Thread
   clazz=env->FindClass("java/lang/Thread");
   //trovo il metodo currentThread
   mid=env->GetStaticMethodID(clazz,"currentThread","()Ljava/lang/Thread;");
   //invocazione
   return env->CallStaticObjectMethod(clazz,mid);
   }
Come si può vedere, si sfruttano le potenzialità offerte da JNI per invocare, all'interno del codice C++, un metodo Java! Questo codice equivale infatti alla chiamata di Thread.getCurrentThread( ) delle API di Java.
Questa tecnica è molto potente, ma anche molto delicata. Tanto per rendere l'idea, se provate a chiamare la funzione getJavaCurrentThread per i thread di sistema nati prima dell'evento JVMPI_EVENT_JVM_INIT_DONE avrete un bel crash!

I giochi sono fatti

Ormai il più è fatto. Il profiler può ricevere gli eventi riguardanti classi, oggetti e metodi, riconoscere il thread che li ha generati e aggiornare le statistiche nella tabella globale thread_table. Ad esempio il seguente stralcio di codice:

  Stat *stat;
  if(ev->event_type==JVMPI_EVENT_METHOD_ENTRY2)
    {
    stat=thread_table.get(ev->env_id);
    stat->method_num++;
    }
indica che si è riconosciuto un evento di chiamata di un metodo, quindi si è cercato il thread proprietario nella tabella thread_table e si è aggiornato il contatore opportuno.

Il seguente codice è un po' più complicato, ma rappresenta un altro punto di forza del monitor.

  //un riferimento ad un metodo Java
  static jmethodID twrite=NULL;
  if(ev->event_type==JVMPI_EVENT_CLASS_LOAD)
    //se la classe si chiama SocketOutputStream
    if(twrite==NULL && (strcmp(ev->u.class_load.class_name,"java/net/SocketOutputStream")==0))
      {
      //cerco il metodo socketWrite
      for(meth=ev->u.class_load.methods;;meth++)
      if(strcmp(meth->method_name,"socketWrite")==0)
        {twrite=meth->method_id;break;}
      }
  if(ev->event_type==JVMPI_EVENT_METHOD_ENTRY2)
    {
    stat=thread_table.get(ev->env_id);
    stat->method_num++;
    if(ev->u.method_entry2.method_id==twrite) stat->tcp_out++;
    }

Nel primo blocco, al caricamento di una classe si verifica che non si tratti della java.net.SocketOutputStream. [Non la troverete nella guida alle API Java perchè si tratta di una classe con visibilità protetta. In effetti quando chiamate Socket.getOutputStream vi viene restituito un oggetto di questo tipo]. Se sì, si cerca il metodo socketWrite, che per la cronaca è un metodo nativo e privato: anche se non lo sapete, eseguire questo metodo è l'unico modo con cui i vostri programmi Java possono scrivere sulle socket TCP.

Il secondo blocco è del tutto simile all'esempio precedente, tranne che per l'ultima riga di codice. E' ora possibile sapere se il metodo in questione è in realtà il metodo socketWrite di un oggetto di tipo SocketOutputStream. Se sì, significa che il thread ha intenzione di scrivere su una socket TCP, e quindi si aggiornerà la statistica corrispettiva.

precedente|continua