CAPITOLO 1 - Il linguaggio Java e la Java Virtual Machine
Dato che essa è, generalmente, implementata per via software, segue che, come risulta evidente
dal seguente schema, l’applicazione Java potrà essere eseguita ovunque tale piattaforma sia presente
e, di conseguenza, i programmi potranno essere elaborati su differenti tipi di computer.
Tutto ciò è reso possibile dal fatto che, in pratica, un programma scritto in linguaggio Java viene
compilato in un file che avrà formato .class, contenente del codice chiamato bytecode, e che non è
specifico per una particolare macchina fisica, ma che è costituito da istruzioni eseguibili dalla Java
Virtual Machine. Questa caratteristica, quindi, lo rende portabile, potendo essere eseguito ovunque
sia presente una macchina virtuale Java. Tale concetto può essere riassunto con lo slogan: “Write
Once, Run Anywhere”.
Infatti, come si evince dalla precedente immagine, la Java Platform può essere implementata su
un gran numero di hosts aventi differenti capacità di elaborazione, dalle embedded device ai
mainframe. Ciò è dovuto al fatto che il progetto originale di Java consisteva nel cercare una
tecnologia che permettesse la distribuzione di software attraverso la rete, ed il suo conseguente
utilizzo su qualunque tipo di sistema, indipendentemente dal processore e sistema operativo
montato.
Per ottenere questo obiettivo, il Java Runtime System (la piattaforma Java) doveva essere
sufficientemente compatto per poter essere implementato anche in un tipico sistema embedded,
adeguandosi, quindi, alle risorse disponibili come: limitata quantità di memoria, non possesso di
hard disk, di display grafico, ecc. Di conseguenza, per soddisfare questi speciali requisiti sono state
create differenti versioni della Java Platform per embedded system:
La Java Personal Platform;
La Java Embedded Platform;
La Java Card Platform.
La differenza principale rispetto alla versione standard sta nella dimensione delle loro librerie
API, che diminuisce a partire da quella della Personal Platform fino a quella della versione per
Smart Card (in quest’ultima è ridotto anche il set di istruzioni della Virtual Machine).
Successivamente, dato che questi subset non soddisfacevano tutte le esigenze, è stata definita una
versione veramente minima delle API chiamata Java 2 Platform Micro Edition (J2ME), e si sono
caratterizzati, poi, una serie di configurazioni (CLDC che è l’acronimo di Connected Limited
Device Configuration, CDC Connected Device Configuration) e profili (MIDP cioè Mobile
Information Device Profile, Personal Profile, Foundation Profile, ecc.), in base al particolare
segmento d’uso della periferica e costituiti da altri subset di API.
CAPITOLO 1 - Il linguaggio Java e la Java Virtual Machine
D’altra parte sono stati definiti, poi, dei superset di API, sviluppati appositamente per le
workstation ed i mainframe, e che hanno portato alla creazione della Java Platform Enterprise
Edition.
In definitiva, quindi, considerando anche la Java 2 Standard Edition, che è dedicata ai desktop, si
hanno i seguenti tre principali set di API:
La Enterprise Edition;
La Standard Edition;
La Micro Edition.
Nel caso di maggior interesse, che è quello della Java 2 Standard Edition (concepita per l’utilizzo
su desktop), il set base di librerie disponibili è detto Standard (o Base) API, mentre quello
considerato opzionale è chiamato Standard Extension API. Quest’ultimo include servizi come:
telefonia, e-commerce, audio, video, grafica 3D, ecc. e molte delle librerie sono anche incluse nella
versione Enterprise Edition.
Diamo uno sguardo all’interno della piattaforma Java illustrata nella seguente figura.
Come si è detto, essa è composta da due parti principali che qui definiamo brevemente: la Java
Virtual Machine e le Java API:
La Java Virtual Machine: è una macchina astratta che può essere implementata per via
software o hardware (JavaChip). Nel primo caso, l’interfaccia di porting e l’adattatore
facilitano il suo trasporto verso nuovi sistemi operativi senza la necessità di dover
completamente ricompilare il codice. Alla Java Virtual Machine è interamente dedicata una
delle prossime sezioni;
Le Java API: formano un’interfaccia standard ed indipendente dal sistema sottostante per le
applets e le applicazioni sviluppate in Java e sono realizzate in modo da fornire al
programma utente l’accesso alle risorse anche attraverso i metodi nativi (come si può vedere
nella figura successiva).
Sull’utilizzo dei metodi nativi nelle applicazioni Java si tornerà ampiamente più avanti.
CAPITOLO 1 - Il linguaggio Java e la Java Virtual Machine
Le API Java possono essere suddivise in:
Le Java Base API: che forniscono la vera base del linguaggio, cioè utilities, I/O, networking,
GUI, servizi applets, ecc. In particolare, definiscono le classi base per la creazione di
applicazioni ed applets pienamente funzionali. Un esempio sono le classi contenute nei
package: java.lang, java.util, java.io, java.net, java.awt ecc.
Le Java Standard Extension API: che estendono le capacità del Java oltre quelle fornite
dalle classi definite nel Java Base API. Ad esempio, forniscono le funzionalità per ciò che
riguarda: telefonia, sicurezza, audio, video, grafica 2D,3D, ecc.
IL LINGUAGGIO JAVA
Il Java è un linguaggio chiaramente di tipo orientato agli oggetti. Inoltre, essendo pensato per lo
sviluppo e la distribuzione attraverso la rete di contenuti eseguibili, deve essere un aspetto
prioritario quello di possedere delle caratteristiche di sicurezza molto efficienti. Infatti, ne sono un
esempio i limiti imposti alle operazioni eseguibili da parte di un’applet (ad esempio non le è
consentito leggere o scrivere file sul sistema dell’utente), le misure restrittive imposte nell’accesso
alla memoria per evitare che possano essere poste in atto tecniche atte a scavalcare le barriere di
sicurezza, ecc. Inoltre, nel runtime environment è presente un componente, chiamato verifier, che
esamina, in fase di esecuzione, ciascun bytecode verificando che non sia stato violato alcun
costrutto del linguaggio (eventualità che si riscontra nel caso in cui il compilatore Java sia stato
alterato). Da ciò emerge che, tuttora, Java è il linguaggio più sicuro in circolazione.
Inoltre, Java è anche molto robusto, dato che impone al programmatore delle restrizioni nella
stesura del codice sorgente, soprattutto per ciò che riguarda l’utilizzo dei puntatori aritmetici e dei
tipi di dati, impedendo, così, l’accesso, casuale o volontario, alla memoria in modo improprio.
Inoltre, vi è un controllo delle eccezioni (condizioni di errore) molto funzionale e la gestione della
CAPITOLO 1 - Il linguaggio Java e la Java Virtual Machine
memoria è organizzata in modo da sollevare il programmatore dalla sua deallocazione (compito che
sarà assolto dal Garbage Collector di cui si parlerà successivamente).
La caratteristica più importante di Java, come si è detto, è, però, la sua portabilità (indipendenza
dalla piattaforma), dato che uno stesso codice compilato può essere eseguito ovunque, a patto che
sul sistema vi sia una Java Virtual Machine in esecuzione.
La programmazione orientata agli oggetti in Java.
Le componenti fondamentali di un programma Java sono: le classi, gli oggetti, i membri
(distinti in attributi o proprietà ed in metodi) ed i package.
Dalla teoria della programmazione orientata agli oggetti si hanno le seguenti definizioni:
la classe è un’astrazione per un insieme di oggetti che abbiano le stesse caratteristiche (cioè
attributi, distinti in variabili e costanti) e funzionalità (ossia metodi).
L’oggetto è un’istanza della classe.
Per quanto riguarda i metodi, essi costituiscono l’unica via per eseguire delle operazioni sui dati
di un oggetto ed il loro utilizzo è caratterizzato da due fasi: la dichiarazione e la chiamata. Nella
prima, il metodo viene definito usando la seguente sintassi:
[modificatori] tipo_di_ritorno nome_del_metodo ([lista parametri]) {corpo_del_metodo}
dove:
Modificatori: sono delle keyword di Java che hanno lo scopo di modificare le funzionalità e
le caratteristiche di un metodo;
Tipo di ritorno: è il tipo di dato che il metodo dovrà eventualmente restituire;
Nome metodo: è l’identificatore utilizzato per le successive chiamate del metodo;
Parametri: sono le variabili che saranno passate al corpo del metodo all’atto della sua
chiamata e che dovranno essere dichiarate in questo punto;
Corpo del metodo: è l’insieme delle operazioni che saranno eseguite quando il metodo verrà
richiamato.
Per ciò che riguarda l’accesso ad un metodo (od ad un attributo), esso avviene tramite l’operatore
dot, nel senso che, tutti i membri (attributi o metodi) pubblici che sono definiti all’interno di una
classe, possono essere accessibili attraverso una sua istanza con la seguente sintassi:
nomeOggetto.nomeMetodo;
oppure
nomeOggetto.nomeAttributo;
Mentre l’accesso ad un metodo di un oggetto provoca l’esecuzione del codice compreso nel suo
corpo, quello ad un attributo ne consente il prelievo, o in taluni casi anche la modifica, del valore.
Per questi ultimi (cioè variabili e costanti) possiamo, come per i metodi, differenziare il loro
utilizzo in due fasi: la dichiarazione e l’assegnazione. In una dichiarazione di variabile o costante la
sintassi da rispettare è la seguente:
[modificatori] tipo_di_dato nome_della_variabile;
CAPITOLO 1 - Il linguaggio Java e la Java Virtual Machine
dove:
I modificatori: sono parole chiave usate per modificare le caratteristiche della variabile (o
della costante);
Il tipo di dato: è il tipo di dato della variabile (o costante);
Il nome della variabile: è l’identificatore della variabile utilizzato per i futuri accessi.
La differenza sostanziale tra una variabile ed una costante in Java risiede nel fatto che
quest’ultima, all’atto della sua definizione, deve possedere final fra i modificatori e deve essere
immediatamente assegnata.
La posizione della dichiarazione di una variabile all’interno del codice, definisce il suo scope,
ovvero la sua visibilità o ciclo di vita. In base alla posizione della dichiarazione possiamo definire
quattro tipi differenti di variabili:
Le variabili statiche: sono dichiarate in una classe all’esterno dei metodi e debbono
possedere fra i propri modificatori: static; non necessitano di istanziazione e condividono lo
stesso ciclo di vita della classe stessa;
Le variabili d’istanza: anche queste sono dichiarate all’esterno dei metodi di una classe, ma
a differenza delle precedenti condividono il proprio ciclo di vita con l’oggetto a cui
appartengono;
Variabili locali: sono anche dette di stack o automatiche e sono quelle dichiarate all’interno
di un metodo. Esse smetteranno di esistere all’uscita del metodo stesso;
Parametri formali: sono quelle variabili dichiarate all’interno delle parentesi tonde, che si
trovano alla destra dell’identificatore nella dichiarazione di un metodo, e sono dette anche
parametri o argomenti del metodo. Un parametro si può anche considerare come una
variabile locale, avendo stessa visibilità e ciclo di vita.
Con l’operazione di assegnazione (usando l’operatore =) ad una variabile viene attribuito un
determinato valore.
Un package ci permette di raggruppare, in un’unica entità complessa, delle classi Java
logicamente correlate. In tal caso, ciascuna di esse dovrà dichiarare nel proprio codice
l’appartenenza al package stesso.
I tipi di dato in Java.
In Java i dati vengono suddivisi in due tipi: quelli primitivi e quelli reference.
Tipi di dato primitivi.
Il linguaggio definisce otto tipi di dato primitivi:
Interi: byte (8 bits), short (16 bits), int (32 bits), long (64 bits);
Floating point (virgola mobile): float (32 bits), double (64 bits);
Testuale: char (16 bits formato Unicode);
Logico-booleano: boolean (1 bit).
CAPITOLO 1 - Il linguaggio Java e la Java Virtual Machine
Tipi di dato non primitivi: i reference.
In Java, la creazione di un oggetto appartenente ad una data classe, avviene in due fasi: la
dichiarazione e l’inizializzazione.
Nella prima, si utilizza una sintassi del tipo:
NomeClasse NomeOggetto;
mentre, per l’inizializzazione la sintassi da seguire è di seguito riportata:
NomeOggetto = new NomeOggetto();
dove si ricorre alla keyword new. Come si vede, la fase di dichiarazione di un oggetto è del tutto
simile a quella di un tipo di dato primitivo. Il nome che si assegna all’oggetto è detto “reference”,
dato che non si può considerare propriamente una variabile contenente il dato stesso. La definizione
esatta di reference cambia in base alla piattaforma, però, convenzionalmente, si tende a definirlo
come una variabile contenente principalmente le due seguenti informazioni: un indirizzo che punta
all’area di memoria contenente l’oggetto istanziato ed il suo intervallo di puntamento, il quale ci
consente di poter accedere all’interfaccia pubblica dell’istanza (ossia a tutti i membri pubblici
dichiarati nella sua classe di appartenenza).
Quindi, alla luce di ciò che si è appena detto, le seguenti due assegnazioni portano a risultati
differenti:
NomeClasse NomeOggetto1 = NomeOggetto2;
Tipo dato1= dato2;
Infatti, nel primo caso entrambi i reference punteranno al medesimo oggetto, per cui, qualunque
modifica sia apportata tramite un reference sarà verificabile anche tramite l’altro, mentre, nel
secondo caso, le due variabili assumeranno il medesimo valore pur rimanendo completamente
indipendenti e, nel caso in cui una di esse fosse modificata, l’altra non subirebbe variazioni.
In Java, bisogna poi porre particolare attenzione all’operazione del passaggio di parametri all’atto
dell’invocazione di un metodo. Infatti, tale operazione avviene sempre per valore e, quindi, ciascun
parametro otterrà soltanto una copia della variabile da passare. Questo comporta che il valore in
essa contenuto rimarrà invariato anche dopo l’esecuzione del metodo in questione.
A questo punto bisogna effettuare, però, una distinzione tra il caso in cui la variabile da passare
sia di tipo primitivo, dove le eventuali modifiche all’interno del metodo lasceranno invariato il dato
contenuto nella variabile presente all’esterno, e quello in cui essa sia di tipo reference, dove,
attraverso il metodo invocato, si potranno effettuare anche variazioni agli attributi dell’oggetto
referenziato, che in tal caso rimarranno anche una volta ritornati al metodo chiamante.
Due importanti tipi reference sono le stringhe e gli arrays. Infatti, in Java le stringhe, a differenza
dalla maggior parte dei linguaggi di programmazione, non sono arrays di caratteri (char), ma oggetti
veri e propri.
Lo stesso discorso vale anche per gli arrays che, essendo una collezione di dati (di tipo primitivo,
di tipo reference oppure di altri array), sono anch’essi degli oggetti.
I concetti base, su cui si fonda il paradigma orientato agli oggetti di Java, sono:
Incapsulamento;
Ereditarietà;
Polimorfismo.
CAPITOLO 1 - Il linguaggio Java e la Java Virtual Machine
Incapsulamento.
L’incapsulamento è di fondamentale importanza per la programmazione ad oggetti, dato che
consente ad una classe di acquisire le proprietà di robustezza, indipendenza e riusabilità. La filosofia
alla base dell’incapsulamento si fonda sul controllo dell’accesso ai membri di una classe. Questo si
implementa fornendo l’accesso ai dati di una classe soltanto attraverso un’interfaccia pubblica, cioè
costituita da metodi dichiarati usando il modificatore public, che li renderà invocabili dall’esterno
(cioè da oggetti appartenenti ad altre classi). Tutti i restanti membri, cioè quelli non appartenenti
all’interfaccia, dovranno essere dichiarati private, in modo da essere inaccessibili dall’esterno, ma
soltanto da oggetti appartenenti alla classe stessa. Questa proprietà consente di avere dati
raggiungibili sempre e soltanto attraverso un’interfaccia pubblica, rendendo, così, la classe
indipendente e riutilizzabile; inoltre, essendo bloccato l’accesso diretto a tutti gli attributi privati,
ciò comporta anche una maggiore robustezza del software.
Oltre agli specificatori d’accesso private e public, esiste anche il modificatore protected, che
consente l’accesso al membro, soltanto a quegli oggetti appartenenti a classi dello stesso package di
quella in cui è dichiarato.
Ereditarietà.
Uno dei fondamenti, che stanno alla base della programmazione ad oggetti, è il concetto di
ereditarietà. Essa è la facoltà di una classe di poter ereditare i membri di un’altra, consentendo di
sfruttarne il codice già scritto (e testato). Utilizzando l’ereditarietà, infatti, si può creare una nuova
classe (detta derivata o sottoclasse) a partire da una già preesistente detta superclasse (o classe base).
In particolare, tutti i membri dichiarati pubblici nella superclasse lo saranno anche nella classe
derivata. Inoltre, una volta ereditati potranno anche essere ridefiniti in modo da variarne il
comportamento (operazione detta: override). Invece, i membri dichiarati privati nella superclasse
non saranno direttamente accessibili (e quindi ridefinibili) in quella derivata. Appare ovvio, inoltre,
che nella definizione della nuova sottoclasse si potranno aggiungere nuovi membri consentendo,
così, una specializzazione della classe di partenza.
Come si vede, quindi, aumentando le funzionalità della superclasse in quella derivata,
l’ereditarietà ne consente un riuso del codice, che è uno dei grandi vantaggi della programmazione
ad oggetti, dato che permette un notevole aumento di produttività.
Nella terminologia corrente, una classe derivata si dice anche estendere la relativa superclasse.
In Java non esiste l’ereditarietà multipla del C++, che consente ad una sottoclasse di estendere
più superclassi.
Per specificare che la classe ClasseDerivata estende la ClasseBase si utilizza la parola chiave
extends; ad esempio:
public ClasseDerivata extends ClasseBase
{ ……
}
Polimorfismo.
Il polimorfismo è un concetto che può essere applicato sia ai metodi che alle classi.
Il primo caso, corrisponde all’utilizzare il medesimo identificatore per definire differenti metodi
di una stessa classe. Per precisare meglio la questione, definiamo come segnatura o firma di un
metodo la coppia costituita dal suo identificatore e dalla lista dei suoi parametri (i modificatori ed il
tipo di ritorno non ne fanno parte).
Un metodo, nel linguaggio Java, è univocamente determinato dalla sua firma, quindi non soltanto
dal suo identificatore, ma anche dalla lista dei suoi parametri. Da ciò discende che, in una stessa
CAPITOLO 1 - Il linguaggio Java e la Java Virtual Machine
classe, potranno coesistere più metodi con il medesimo identificatore, purché ognuno di essi abbia
firma differente.
A questo proposito, poi, possiamo distinguere tra overload ed override.
Con overload si intende la possibilità, consentita al programmatore, di definire più metodi col
medesimo nome, ma firma differente, in una stessa classe. Ciò sarà utile allorquando si desidera
definire più metodi che possiedano la medesima funzionalità, ma con differente campo di
applicazione (cioè con una diversa lista di parametri).
Per quanto riguarda l’override, invece, è il termine object oriented adottato per descrivere la
caratteristica delle classi derivate di poter ridefinire un metodo pubblico ereditato dalla superclasse.
Nell’override è necessario rispettare le seguenti regole:
Il metodo ridefinito nella sottoclasse deve possedere la medesima segnatura di quello da
riscrivere altrimenti si opererebbe un overload;
Anche il tipo di ritorno del metodo da ridefinire deve coincidere con quello riscritto;
Lo specificatore d’accesso del metodo ridefinito non può essere più restrittivo di quello
dell’originale.
Le considerazioni precedenti rivelano che in Java il metodo da eseguire in corrispondenza di
un’invocazione dovrà essere individuato dinamicamente in fase di run-time (cioè siamo in presenza
di un linking dinamico) e non durante la compilazione (dove il linking sarebbe statico).
Il polimorfismo per classi, invece, consiste essenzialmente nel poter assegnare ad un reference di
una superclasse un’istanza di una sua classe derivata. Ovviamente, ciò comporta che l’accesso ai
membri definiti nelle classi derivate sia negato, senza un opportuno down-casting.
I modificatori.
Un modificatore è una keyword che, nella definizione di un componente di un’applicazione Java
(classe, metodo o variabile), ne influenza il comportamento. Nello schema seguente si riassumono,
per ciascun componente Java, i principali modificatori che gli si possono applicare.
public protected (default) private final static native abstract
classi Si No Si No Si No No Si
attributi Si Si Si Si Si Si No No
metodi Si Si Si Si Si Si Si Si
costruttori Si Si Si Si No No No No
codice No No Si No No Si No No
Con l’indicazione “default” si segnala la situazione in cui non si antepone alcun modificatore al
componente. Un sottoinsieme del precedente è quello dei modificatori d’accesso (detti anche
specificatori d’accesso) che possono stabilire la visibilità di un componente Java. Abbiamo quattro
possibilità:
public: può essere usato nella definizione di membri (attributi o metodi) o di classi, che
saranno, così, accessibili da qualsiasi oggetto di ogni classe situata in un qualunque
package;
CAPITOLO 1 - Il linguaggio Java e la Java Virtual Machine
protected: è un modificatore applicabile ad un membro, che in tal caso sarà accessibile
soltanto dall’interno dello stesso package, e da tutte le sottoclassi della classe in cui è
definito, anche se esterne al package;
private: questo specificatore d’accesso restringe la visibilità di un membro di una classe
agli oggetti della classe stessa;
default: non anteponendo modificatori d’accesso ad un membro di una classe, per default
esso sarà accessibile soltanto da oggetti di classi appartenenti al package dove è definito.
Altri modificatori molto importanti sono:
final: esso è applicabile sia a membri che a classi:
una variabile dichiarata final diventerà una costante;
un metodo dichiarato final in una classe non può essere riscritto in una eventuale
classe derivata;
una classe dichiarata final non potrà più essere estesa.
static: un membro definito col modificatore static viene condiviso da tutte le istanze della
classe stessa. Quindi, un membro statico ha la caratteristica di poter essere utilizzato
direttamente senza la necessità di istanziazione della classe, mediante una sintassi del
tipo:
nomeClasse.nomeMembro;
L’utilizzo di un membro statico causa direttamente il caricamento in memoria della classe
contenente il membro in questione.
Il modificatore static può essere utilizzato anche su un blocco di codice, che verrebbe in
tal caso definito inizializzatore statico. Tale blocco di codice avrebbe la proprietà di
essere eseguito al momento del caricamento in memoria della classe stessa, addirittura,
prima dell’eventuale costruttore.
LE SPECIFICHE DELLA JAVA VIRTUAL MACHINE
Per cercare di sfruttare al meglio le potenzialità della tecnologia Java, è necessario entrare più nel
dettaglio dell’architettura sulla quale è basata la Java Virtual Machine.
Quest’ultima, essenzialmente, è un computer astratto, definito attraverso delle specifiche. Nella
pratica, ovviamente, si rende necessaria un’implementazione concreta di tali specifiche (attualmente
realizzata da differenti produttori e disponibile per varie piattaforme), che può essere sviluppata o
interamente in software oppure in una combinazione di software ed hardware.
Quando è lanciata un’applicazione Java, viene con essa creata un’istanza di runtime
dell’implementazione della Java Virtual Machine, che completerà il suo ciclo di vita al termine
dell’applicazione stessa. Allo stesso modo, l’esecuzione contemporanea di più programmi Java sul
medesimo calcolatore, creerà differenti istanze della Java Virtual Machine, ciascuna delle quali
associata all’applicazione corrispondente.
Nelle specifiche della Java Virtual Machine, ciascuna istanza è descritta attraverso una
suddivisione in sottosistemi, aree di memoria, tipi di dati ed istruzioni, ecc.
Nello schema a blocchi seguente sono rappresentati i sottosistemi e le aree di memoria più
importanti che vengono riportati nelle specifiche.
0
CAPITOLO 1 - Il linguaggio Java e la Java Virtual Machine
Come si nota, ogni Java Virtual Machine possiede un class loader, che si occupa di caricare le
classi e le interfacce necessarie all’esecuzione dell’applicazione ed un execution engine, che è quel
meccanismo adibito all’elaborazione delle istruzioni contenute nei metodi delle classi caricate. La
Java Virtual Machine, inoltre, per l’esecuzione delle applicazioni necessita di memoria su cui
immagazzinare sia i bytecode (istruzioni), che tutte le altre informazioni estratte dalle classi
caricate, gli oggetti istanziati, i parametri dei metodi, le variabili locali, i risultati intermedi delle
elaborazioni ecc. A tale scopo, la memoria di runtime è organizzata in diverse aree dati, ma, benché
le più importanti siano presenti in qualche modo in ogni implementazione della Java Virtual
Machine, i loro dettagli strutturali possono variare in modo significativo sulla base dei differenti
vincoli esistenti nella specifica piattaforma.
Comunque, in generale, ciascuna istanza della Java Virtual Machine possiede un’area dei metodi
ed un Heap, che sono zone di memoria condivise fra tutti i thread dell’applicazione ad essa
associata. Come viene mostrato nella seguente figura, l’area dei metodi conterrà le informazioni
estratte dalle classi caricate, mentre nell’Heap saranno memorizzati tutti gli oggetti istanziati
durante l’esecuzione dell’applicazione.
1
CAPITOLO 1 - Il linguaggio Java e la Java Virtual Machine
A ciascun thread, creato in un programma in esecuzione, viene assegnato un proprio program
counter (pc register) ed uno stack Java. Il pc register conterrà l’indirizzo della prossima istruzione
da eseguire purché, ovviamente, il thread stia elaborando del codice Java e non un metodo nativo.
Lo stack Java assegnato al thread, invece, conterrà, per ciascun metodo in esecuzione, le sue
variabili locali, i parametri con i quali è stato invocato, l’eventuale valore di ritorno, le elaborazioni
intermedie, ecc. Infatti, la Java Virtual Machine, non possedendo dei registri interni per mantenere i
risultati intermedi delle elaborazioni, deve far ricorso direttamente alla memoria RAM, che è
organizzata come uno stack Java. Questo approccio, è stato adottato dai progettisti per una
molteplicità di ragioni, quali: mantenere il set di istruzioni compatto ed al contempo facilitare, pur
cercando di non penalizzare eccessivamente le prestazioni complessive, l’implementazione delle
specifiche attraverso una struttura di registri svincolata dal particolare sistema. La diminuzione delle
prestazioni, in questo caso, è inevitabile ed è dovuta al fatto che anche i registri interni alla Virtual
Machine saranno soltanto virtuali e non dei veri e propri registri di un processore che, per
costruzione, risultano sostanzialmente più veloci della memoria esterna. Inoltre, adottando la
soluzione dello stack, l’architettura della Java Virtual Machine faciliterà l’operazione di
ottimizzazione del codice nel caso in cui siano presenti dei compilatori dinamici che operino in run-
time.
Lo stack Java è suddiviso in frames (ciascuno dei quali contiene lo stato del metodo invocato).
Quando un thread effettua l’invocazione di un metodo, la Virtual Machine inserirà il relativo frame
nello stack, che verrà poi scartato al completamento del metodo stesso.
Lo stato delle invocazioni dei metodi nativi, invece, è memorizzato in un altro stack, che è
chiamato: native method stack.
Nella seguente figura è rappresentata un’istantanea della Java Virtual Machine dove sono in
esecuzione tre differenti thread. In particolare, sono rappresentate le aree di memoria che vengono
create in maniera indipendente per ciascun thread. Tali aree sono private ed esclusive, quindi,
nessuno dei thread può aver accesso al pc register od allo stack Java di un altro.
Come si vede in figura, i thread uno e due stanno eseguendo un metodo Java, mentre il numero
tre un metodo nativo. Ciò comporta, che i registri pc dei primi due thread possiedano l’indirizzo
2
CAPITOLO 1 - Il linguaggio Java e la Java Virtual Machine
dell’istruzione Java del metodo corrente ancora da eseguire, mentre il terzo, dovendo elaborare del
codice nativo, possiede un pc register dal contenuto indefinito.
Addentriamoci ora più nel dettaglio della descrizione dei diversi sottosistemi ed aree dati in cui è
organizzata la Java Virtual Machine.
Il Class Loader.
Il compito principale della Java Virtual Machine è quello di caricare i files .class ed eseguire il
codice (bytecode) in essi contenuto.
Come si può vedere nella seguente figura, la Java Virtual Machine possiede un Class Loader, che
ha, appunto, il compito di caricare i files .class appartenenti o al programma utente oppure alle API
Java necessarie alla corretta esecuzione dell’applicazione.
3
2004 - Realizzato da Leonardo Minnozzi