Macchine virtuali e JVM
Introduzione
Un calcolatore digitale è dotato di una serie di circuiti elettronici che sono in
grado di riconoscere ed eseguire solo un insieme limitato di istruzioni elementari.
Tutti i programmi devono essere convertiti in sequenze di tali istruzioni, prima di
essere eseguiti: l’insieme (set) di istruzioni elementari di un processore è detto
linguaggio macchina.
Scrivere programmi in linguaggio macchina è complesso, principalmente per due
motivi:
 le istruzioni sono molto “semplici” (elementari), pertanto per realizzare
un’azione complessa è necessario scrivere numerose istruzioni in
linguaggio macchina;
 le istruzioni sono in forma binaria.
Il problema viene risolto progettando un nuovo linguaggio, costituito da un
insieme di istruzioni che risulti più conveniente da usare. Indicando con L0 il
linguaggio macchina e con L1 il nuovo linguaggio, possiamo osservare che, tra L0
ed L1 deve esistere una corrispondenza che ad ogni istruzione di L1 associ una o
più istruzioni di L0.
Esempio
La semplice azione di “telefonare un amico”, espressa nel linguaggio L1 da
un’unica istruzione, corrisponde alla seguente sequenza di istruzioni del
linguaggio L0:
1. alzare la cornetta
2. accertarsi che la linea sia libera
3. comporre il numero
4. parlare
5. abbassare la cornetta per chiudere la comunicazione
Il linguaggio L1, per come è stato definito, costituisce un’astrazione rispetto al
linguaggio L0.
Linguaggio L1
Telefonare a un amico
Traduzione
Linguaggio L0
1. Alzare la cornetta
2. Linea libera?
3. Comporre il numero
4. Parlare
5. Chiudere la comunicazione
Per eseguire i programmi scritti nel linguaggio L1 è necessario disporre di un
metodo per passare dalle istruzioni in linguaggio L1 alle corrispondenti istruzioni
in linguaggio L0 (le uniche che la macchina può eseguire). Il passaggio da L1 a
L0 può avvenire in due diversi modi:
1. mediante traduzione;
2. mediante interpretazione.
Autore: Cinzia Bocchi
Ultimo aggiornamento: 08/08/11
1
Traduttori
La traduzione sostituisce ogni istruzione del linguaggio L1 con l’equivalente
sequenza di istruzioni di L0 e genera un nuovo programma nel linguaggio L0. Il
calcolatore eseguirà il nuovo programma invece del vecchio programma in L1.
La traduzione è svolta da un modulo software chiamato compilatore.
Un compilatore è un programma traduttore che riceve in ingresso un programma
scritto in linguaggio ad alto livello (codice sorgente) e produce in uscita il suo
equivalente in linguaggio macchina (codice oggetto), memorizzandolo in un
file. Così un programma compilato una sola volta può essere eseguito quante
volte si vuole.
La compilazione deve essere seguita da un’ulteriore operazione detta linking
(collegamento), svolta da un apposito programma chiamato linker. Tale
operazione consiste nell’aggiungere al programma compilato i moduli software
che realizzano le funzioni richieste dai vari comandi (librerie) e
contemporaneamente nel risolvere i riferimenti a celle di memoria o a variabili.
Alla fine di questo lavoro si ottiene il programma eseguibile.
Codice sorgente
(scritto in linguaggio L1)
Codice oggetto
(scritto in linguaggio macchina L0)
Compilatore
Codice eseguibile
Linker
La traduzione nel linguaggio macchina avviene a patto che il codice sorgente sia
formalmente corretto, cioè rispetti le regole del linguaggio scelto. Ogni volta che
questo non si verifica, viene emesso un messaggio di errore. Il modulo software
che si occupa del controllo degli errori sintattici è chiamato analizzatore
sintattico (parser).
Gli errori possono riguardare l’uso di termini non appartenenti al linguaggio
(errori di tipo lessicale) oppure la costruzione di frasi non corrette dal punto di
vista delle regole grammaticali (errori di tipo sintattico).
Normalmente il compilatore non è in grado di rilevare errori logici, riguardanti la
correttezza dell’algoritmo. Così come la compilazione non può rilevare situazioni
di errore che si possono verificare durante l’esecuzione del programma (errori
runtime), ad esempio la divisione per zero.
E’ importante sottolineare che, dato un linguaggio, non esiste un solo
compilatore ma tanti quante sono le macchine sulle quali si desidera eseguire i
programmi nel dato linguaggio. Ogni compilatore, infatti, traduce il codice
sorgente nel codice macchina di una particolare piattaforma hardware.
Interpreti
L’interpretazione consiste nello scrivere un programma in L0, chiamato
interprete, che prende il programma scritto in linguaggio L1 come input e,
contemporaneamente, traduce in L0 ed esegue un’istruzione alla volta.
Autore: Cinzia Bocchi
Ultimo aggiornamento: 08/08/11
2
Istruzione 1
in linguaggio L1
Istruzione 2
in linguaggio L1
Istruzione n
in linguaggio L1
Interpretazione
Interpretazione
Interpretazione
Istruzione 1
in linguaggio L0
Istruzione 2
in linguaggio L0
Istruzione n
in linguaggio L0
Esecuzione
Esecuzione
Esecuzione
Il processo di interpretazione è svolto da un modulo software chiamato
interprete. Esso accetta in input un programma sorgente e, invece di tradurlo
completamente, ne analizza ogni singola istruzione, la trasforma in una sequenza
di istruzioni in codice macchina e la esegue immediatamente.
Eventuali errori formali vengono rilevati e segnalati solo quando l’istruzione
errata viene tradotta e causano l’interruzione dell’esecuzione.
L’esecuzione di un programma interpretato è in genere più lunga di quella dello
stesso programma compilato. La prima osservazione che possiamo fare è che,
mentre un programma compilato può essere eseguito immediatamente, senza
un’ulteriore fase di traduzione, un programma interpretato non può essere
eseguito immediatamente ma deve sempre essere preventivamente tradotto
istruzione per istruzione. Inoltre, dato che le fasi di traduzione ed esecuzione in
un programma interpretato, sono mescolate tra loro risulta evidente che, se un
programma prevede l’esecuzione di una stessa istruzione per più volte (ad
esempio in un ciclo), questa dovrà essere ogni volta tradotta, allungando così
sensibilmente il tempo di esecuzione dell’intero programma.
Esempio
La differenza tra il processo di traduzione e quello di interpretazione è la stessa
che si osserva fra la traduzione di un documento da una lingua ad un’altra e la
traduzione simultanea dalla lingua di un probabile relatore alla lingua dei suoi
interlocutori. Nel primo caso il traduttore produce un altro documento; nel
secondo caso l’interprete non scrive alcunché ma si limita a tradurre le frasi, una
alla volta.
Macchine virtuali
Per rendere pratico il processo di traduzione è necessario che L0 ed L1 non
differiscano troppo; questo però fa si che anche L1 non sia molto semplice da
usare per l’utente. In quest’ottica, possiamo costruire altri linguaggi (L2, L3, L4,
ecc.) che rappresentino un’ulteriore astrazione rispetto al linguaggio del livello
sottostante e che siano, di volta in volta, più orientati all’utente.
La creazione di linguaggi più facili da usare può continuare fino al
raggiungimento di un linguaggio ideale.
Autore: Cinzia Bocchi
Ultimo aggiornamento: 08/08/11
3
Ogni linguaggio definisce una macchina virtuale, intesa come quella macchina
che può eseguire tutti i programmi scritti in quel linguaggio e viceversa. Ogni
linguaggio Li usa il precedente, Li-1, come base.
Possiamo allora pensare ad un elaboratore come ad una macchina dotata di n
livelli, uno sopra l’altro, a ciascuno dei quali corrisponde un linguaggio e una
macchina virtuale.
I programmi scritti in un linguaggio diverso dal linguaggio macchina, dovranno
subire o una serie di traduzioni, fino ad arrivare al linguaggio L0, oppure
dovranno essere interpretati da un programma interprete, collocato al livello
immediatamente inferiore, fino a giungere ad un interprete di livello L0.
Ad ogni livello non ci si deve preoccupare dei livelli sottostanti, ma si possono
offrire funzionalità al livello sovrastante.
Livello n
Macchina virtuale Mn
…….
Livello 1
Macchina virtuale M1
Livello 0
Macchina virtuale M0
CPU
Il linguaggio del livello n è detto linguaggio di programmazione ad alto
livello. Java è un linguaggio di programmazione ad alto livello.
Evoluzione dei linguaggi ad alto livello
I moderni linguaggi di programmazione sono di tipo evoluto nel senso che
utilizzano termini simili al linguaggio naturale, facilitando così il lavoro del
programmatore. I linguaggi di programmazione possono essere orientati a
specifiche applicazioni o classi di problemi, oppure possono essere adatti per
risolvere qualsiasi problema (linguaggi general purpose). Segue una breve e
non esaustiva panoramica dei linguaggi ad alto livello.
FORTRAN (1956): FORmula TRANslation, nato per applicazioni tecnicoscientifiche e calcolo numerico.
COBOL (1960): Common Business Oriented Language, per applicazioni di tipo
commerciale e gestionale.
ALGOL ( a partire dagli anni ’60): ALGOrithmic Language, per applicazioni
scientifiche.
LISP ( a partire dagli anni ’60): per applicazioni nel campo dell’intelligenza
artificiale.
BASIC (1964): Beginners All-purpose Symbolic Instruction Code, linguaggio
general purpose; si è sviluppato contemporaneamente al diffondersi dei piccoli
sistemi di elaborazione interattivi.
RPG (1966): Report Program Generator, per applicazioni di tipo commerciale e
in particolare per la preparazione di prospetti.
PASCAL (1971): general purpose, orientato ai principi della programmazione
strutturata e della tecnica di analisi top-down.
Autore: Cinzia Bocchi
Ultimo aggiornamento: 08/08/11
4
C (1974): linguaggio general purpose, utilizzato anche nell’ambito dello sviluppo
di sistemi operativi e del software di base; si è diffuso insieme al sistema
operativo UNIX.
PROLOG (1972): PROgramming in LOGic, utilizzato nel campo dell’intelligenza
artificiale, sfrutta le relazioni logiche tra i dati.
C++ (1979): linguaggio general purpose, a oggetti.
JAVA (1995): general purpose a oggetti, utilizzato inoltre per applicazioni che
possono funzionare in modo interattivo sulla rete Internet.
C# (2001): general purpose a oggetti, sviluppato da Microsoft all’interno del
progetto .NET.
I vantaggi derivanti dall’uso di un linguaggio ad alto livello sono:
• facilità d’uso e di apprendimento,
• indipendenza dalla macchina che facilità la portabilità dei programmi,
• facilità di ricerca e di eliminazione degli errori,
• aumento della leggibilità del programma e, quindi, alto livello
documentazione dello stesso.
di
In generale, quindi, l’uso dei linguaggi di programmazione ad alto livello ha
consentito un significativo miglioramento di produttività nella realizzazione del
software, una diminuzione dei costi e un’esplosione dell’informatica in tutti i
settori.
L’approccio Java
Java è un linguaggio compilato e interpretato. Il codice sorgente, contenuto in un
file con estensione .java, per poter essere eseguito deve subire due
trasformazioni:
 una compilazione,
 una interpretazione.
La compilazione è a carico del compilatore javac, che traduce il codice sorgente
in un codice intermedio detto bytecode. Il codice intermedio prodotto, che viene
collocato in un file con lo stesso nome del file .java, ma estensione .class, è
indipendente dalla piattaforma. In altre parole, il bytecode è sempre lo stesso su
ogni macchina. Il bytecode è quindi un codice oggetto le cui istruzioni sono
scritte in uno speciale linguaggio che deve essere successivamente interpretato
da una macchina virtuale, denominata Java Virtual Machine (JVM). La JVM
provvede poi a tradurre ogni istruzione del bytecode nel codice macchina
opportuno e ad eseguirla. Pertanto esistono diverse JVM, dipendenti dalla
piattaforma utilizzata.
Codice sorgente
f ile .jav a
By tecode
f ile .class
IN
OUT
JVM
Compilatore
javac
Interpretazione
Autore: Cinzia Bocchi
Ultimo aggiornamento: 08/08/11
5
Questo doppio passaggio di traduzione è una delle caratteristiche che ha
contribuito al successo di Java, garantendo l’indipendenza del linguaggio stesso
dalla piattaforma. La JVM è inclusa in tutti i principali browser e nel JDK. Se non
si è interessati a sviluppare codice Java e quindi non si possiede il JDK, è
possibile installare solo il Java Runtime Environment (JRE).
JDK e JRE sono scaricabili gratuitamente all’url
http://www.oracle.com/technetwork/java/javase/downloads/index.html
_______________________________________________________________________
Quest'opera è stata rilasciata con licenza Creative Commons Attribution-ShareAlike 3.0 Unported. Per leggere una copia della
licenza visita il sito web http://creativecommons.org/licenses/by-sa/3.0/ o spedisci una lettera a Creative Commons, 171 Second
Street, Suite 300, San Francisco, California, 94105, USA.
Autore: Cinzia Bocchi
Ultimo aggiornamento: 08/08/11
6