Java/La macchina virtuale

Wikibooks, manuali e libri di testo liberi.
Indice del libro

Architettura[modifica]

Due linguaggi[modifica]

L'architettura fa sì che, di fatto, esistano due linguaggi distinti:

  • il linguaggio Java, quello in cui sono scritti i sorgenti di un programma Java.
  • il class file format, in cui sono scritti i file risultanti dalla compilazione.

Il secondo è stato progettato per essere il più compatto possibile (per ridurre al minimo le dimensioni dei file compilati). Esso è stato progettato anche per avere una certa indipendenza dallo stesso linguaggio Java; questo permette anche ad altri linguaggi di essere compilati in questo formato.

Il linguaggio Java[modifica]

Caratteristiche di progettazione

Java è stato progettato partendo da zero, nessuna compatibilità con il passato doveva essere rispettata: questo ha permesso ai progettisti di fare un linguaggio che rispondesse alle più moderne esigenze di programmazione.

Progettato per creare software altamente affidabile, fornisce ampi controlli in fase di compilazione, seguito da ulteriori controlli in fase di esecuzione. Il linguaggio rappresenta una guida ai programmatori verso l’abitudine a produrre programmi affidabili: gestione automatica della memoria, nessun puntatore da gestire, nessun codice "oscuro".

Java è nato per operare in ambiente distribuito, ciò significa che l’argomento sicurezza e di grande importanza. È stata dedicata particolare attenzione alla sicurezza sia a livello di linguaggio sia a livello di sistema run-time. Java permette di costruire applicazioni che possono difficilmente essere invase da altre applicazioni.

L’architettura neutrale di Java è solo una parte di un sistema veramente portabile. Java fa fare alla portabilità un passo avanti, precisando e specificando la grandezza dei tipi di dati e il comportamento degli operatori aritmetici, i programmi sono gli stessi su ogni piattaforma, non ci sono incompatibilità di tipi di dati attraverso diverse architetture hardware e software.

Le prestazioni sono sempre da considerare: Java ottiene ottime prestazioni adottando uno schema attraverso il quale l’interprete può eseguire i bytecode alla massima velocità senza necessariamente controllare l’ambiente run-time, anche grazie a tecnologie come il Just In Time Compiler. Una applicazione automatica, il garbage collector, eseguita in background si occupa inoltre di liberare periodicamente la memoria inutilizzata dai processi e quindi assicura, con elevata probabilità, che la memoria richiesta dalle applicazioni sia sempre disponibile.

La capacità multithreading di Java fornisce i mezzi per costruire applicazioni con più attività concorrenti: Java supporta il multithreading a livello di linguaggio con aggiunta di sofisticate primitive di sincronizzazione; inoltre il sistema Java è stato scritto per essere sicuro nella gestione del multithreding: delle funzionalità provvedono che le librerie siano disponibili senza conflitti tra thread concorrenti in esecuzione.

Modifiche separate

Uno degli aspetti più interessanti è che l'aggiunta di un nuovo costrutto al linguaggio non sempre comporta la modifica della specifica della macchina virtuale o la modifica delle macchine virtuali esistenti. Ad esempio, buona parte delle aggiunte che sono state introdotte con la versione 5 della piattaforma sono state progettate in modo che il compilatore possa compilare i nuovi costrutti del linguaggio usando istruzioni "vecchie" nel bytecode.[1]

Un modo semplice per ottenere questo è progettare costrutti che siano "zucchero sintattico" per costrutti preesistenti e che già hanno un corrispettivo nel bytecode. In altre parole, vengono resi disponibili nuovi costrutti che rappresentano un modo più comodo e semplice di scrivere le stesse cose.

I vantaggi principali di questo approccio:

  • non è necessario modificare la macchina virtuale preesistente per supportare i nuovi costrutti;
  • tende a mantenere i due linguaggi semplici da imparare, in quanto i nuovi costrutti si basano su quelli vecchi e già familiari;
  • è possibile sviluppare indipendentemente la specifica di linguaggio e la specifica della macchina virtuale.

La macchina virtuale[modifica]

Concretizza un modello di elaboratore astratto. Tra i vantaggi di questo approccio:

  1. facilita la realizzazione di programmi portabili su piattaforme diverse.
  2. è possibile rendere l'esecuzione del codice più sicura e il programma più robusto, poiché il programma viene eseguito in un ambiente controllato, isolato dal sistema operativo.
Portabilità

Il primo punto è dovuto al fatto che alcuni aspetti tecnici del modello astratto rendono completamente prevedibili determinati aspetti legati all'esecuzione del programma; questo non avviene in tutti i linguaggi. Ad esempio, le dimensioni dei tipi primitivi in Java sono le stesse su tutte le piattaforme (e sono ben definite a priori), cosa che non avviene in C.

Isolamento dal sistema

Sicurezza. La macchina virtuale ha controllo completo sul codice che viene eseguito (perché il modello astratto su cui è stato scritto il programma è stato progettato proprio per questo) e ciò rende facile bloccare l'esecuzione di codice ritenuto non sicuro (o potenzialmente tale). Questo non sarebbe possibile se il programma interagisse direttamente con il sistema operativo, anziché con una macchina virtuale.

Robustezza. Ecco un esempio: una delle caratteristiche più note della JVM è che essa effettua in automatico il range check su tutti gli accessi agli array. In linguaggi come il C, il codice del programma è libero di creare un array di una certa grandezza e leggere o scrivere al di fuori dei suoi limiti effettivi. Si tratta di un'operazione pericolosa, perché può potenzialmente portare a risultati non voluti dal programmatore, e, soprattutto, non prevedibili a priori. Invece, la macchina virtuale reagisce a questo comportamento erroneo con una reazione del tutto prevedibile e, soprattutto, gestibile dal programma: ciò che va "in crash" è la singola riga di codice (in realtà si dice che è stata sollevata un'eccezione), l'operazione pericolosa non viene eseguita, e il programma (informato dell'errore) può decidere se proseguire o terminare.

Esecuzione[modifica]

La natura astratta del modello di macchina virtuale permette l'implementazione di sistemi molto diversi per eseguire un programma Java:

  1. interpretato
  2. compilato in codice nativo
  3. soluzione mista (interprete dotato di un compilatore, chiamato JITter, che a run-time compila in codice nativo determinate parti del programma)
  4. macchina virtuale implementata a livello hardware (ovvero: viene realizzato un vero e proprio processore il cui instruction set coincide con il repertorio delle istruzioni definito dal modello di macchina astratto)

Il primo sistema è, generalmente, il più lento, a causa dello overhead dovuto alla presenza dell'interprete.

La soluzione adottata per default dalle macchine virtuali distribuite dalla Sun a gennaio 2010 è la terza. Essa permette di raggiungere prestazioni in genere paragonabili a quelle di un programma scritto ad esempio in C e compilato direttamente in codice nativo.


Note[modifica]

  1. Questo non è stato possibile per tutto: ad esempio, restano escluse da questo meccanismo le annotazioni.