Lo stile di vita dei file di classe Java

Benvenuti a un'altra puntata di "Under the Hood". Nell'articolo del mese scorso ho discusso della Java Virtual Machine, o JVM, il computer astratto per il quale sono compilati tutti i programmi Java. Se non hai familiarità con la JVM, potresti leggere l'articolo del mese scorso prima di questo. In questo articolo fornisco uno sguardo alla struttura di base e allo stile di vita del file di classe Java.

Nato per viaggiare

Il file di classe Java è un formato definito con precisione per Java compilato. Il codice sorgente Java viene compilato in file di classe che possono essere caricati ed eseguiti da qualsiasi JVM. I file di classe possono viaggiare attraverso una rete prima di essere caricati dalla JVM.

Infatti, se stai leggendo questo articolo tramite un browser compatibile con Java, i file di classe per l'applet di simulazione alla fine dell'articolo stanno volando attraverso Internet al tuo computer in questo momento. Se desideri ascoltarli (e il tuo computer ha funzionalità audio), premi il seguente pulsante:

È necessario un browser abilitato per Java per visualizzare questa applet

Sembra che si stiano divertendo, eh? È nella loro natura. I file di classe Java sono stati progettati per viaggiare bene. Sono indipendenti dalla piattaforma, quindi saranno i benvenuti in più posti. Contengono bytecode, il set compatto di istruzioni per la JVM, in modo che possano viaggiare leggeri. I file di classe Java scorrono costantemente attraverso le reti a una velocità vertiginosa per arrivare alle JVM di tutto il mondo.

Cosa c'è in un file di classe?

Il file di classe Java contiene tutto ciò che una JVM deve sapere su una classe o interfaccia Java. Nell'ordine di apparizione nel file di classe, i componenti principali sono: magia, versione, pool di costanti, flag di accesso, questa classe, super classe, interfacce, campi, metodi e attributi.

Le informazioni memorizzate nel file di classe spesso variano in lunghezza, ovvero non è possibile prevedere la lunghezza effettiva delle informazioni prima di caricare il file di classe. Ad esempio, il numero di metodi elencati nel componente metodi può differire tra i file di classe, perché dipende dal numero di metodi definiti nel codice sorgente. Tali informazioni sono organizzate nel file di classe anteponendo alle informazioni effettive la sua dimensione o lunghezza. In questo modo, quando la classe viene caricata dalla JVM, viene letta per prima la dimensione delle informazioni di lunghezza variabile. Una volta che la JVM conosce la dimensione, può leggere correttamente le informazioni effettive.

Le informazioni vengono generalmente scritte nel file di classe senza spazio o riempimento tra parti consecutive di informazioni; tutto è allineato sui limiti dei byte. Questo aiuta a mantenere piccoli i file di classe in modo che siano aerodinamici mentre volano attraverso le reti.

L'ordine dei componenti del file di classe è rigorosamente definito in modo che le JVM possano sapere cosa aspettarsi e dove aspettarselo durante il caricamento di un file di classe. Ad esempio, ogni JVM sa che i primi otto byte di un file di classe contengono i numeri magici e di versione, che il pool di costanti inizia sul nono byte e che i flag di accesso seguono il pool di costanti. Ma poiché il pool di costanti è di lunghezza variabile, non conosce l'esatta ubicazione dei flag di accesso finché non ha finito di leggere nel pool di costanti. Una volta terminata la lettura nel pool di costanti, sa che i prossimi due byte saranno i flag di accesso.

Magic e numeri di versione

I primi quattro byte di ogni file di classe sono sempre 0xCAFEBABE. Questo numero magico rende i file di classe Java più facili da identificare, perché le probabilità sono ridotte che i file non di classe inizino con gli stessi quattro byte iniziali. Il numero è chiamato magia perché può essere tirato fuori da un cappello dai progettisti del formato di file. L'unico requisito è che non sia già utilizzato da un altro formato di file che potrebbe essere riscontrato nel mondo reale. Secondo Patrick Naughton, un membro chiave del team Java originale, il numero magico è stato scelto "molto prima che il nome Java fosse mai pronunciato in riferimento a questo linguaggio. Stavamo cercando qualcosa di divertente, unico e facile da ricordare. È solo una coincidenza che OxCAFEBABE, un riferimento indiretto ai simpatici baristi del Peet's Coffee, prefigurava il nome Java. "

I secondi quattro byte del file di classe contengono i numeri di versione principale e secondaria. Questi numeri identificano la versione del formato del file di classe a cui aderisce un particolare file di classe e consentono alle JVM di verificare che il file di classe sia caricabile. Ogni JVM ha una versione massima che può caricare e le JVM rifiuteranno i file di classe con le versioni successive.

Pool costante

Il file di classe memorizza le costanti associate alla sua classe o interfaccia nel pool di costanti. Alcune costanti che possono essere viste giocare nel pool sono stringhe letterali, valori finali delle variabili, nomi di classi, nomi di interfacce, nomi e tipi di variabili e nomi e firme di metodi. La firma di un metodo è il tipo restituito e l'insieme di tipi di argomenti.

Il pool di costanti è organizzato come un array di elementi di lunghezza variabile. Ogni costante occupa un elemento dell'array. In tutto il file di classe, le costanti sono indicate dall'indice intero che indica la loro posizione nell'array. La costante iniziale ha un indice di uno, la seconda ha un indice di due, ecc. L'array del pool di costanti è preceduto dalla dimensione dell'array, quindi le JVM sapranno quante costanti aspettarsi durante il caricamento del file di classe.

Ogni elemento del pool di costanti inizia con un tag di un byte che specifica il tipo di costante in quella posizione nell'array. Una volta che una JVM acquisisce e interpreta questo tag, sa cosa segue il tag. Ad esempio, se un tag indica che la costante è una stringa, la JVM si aspetta che i due byte successivi siano la lunghezza della stringa. Seguendo questa lunghezza di due byte, la JVM si aspetta di trovare il numero di lunghezza di byte, che costituiscono i caratteri della stringa.

Nel resto dell'articolo a volte mi riferirò all'ennesimo elemento dell'array del pool di costanti come costante_pool [n]. Ciò ha senso nella misura in cui il pool di costanti è organizzato come un array, ma tieni presente che questi elementi hanno dimensioni e tipi diversi e che il primo elemento ha un indice di uno.

Flag di accesso

I primi due byte dopo il pool di costanti, i flag di accesso, indicano se questo file definisce o meno una classe o un'interfaccia, se la classe o l'interfaccia è pubblica o astratta e (se è una classe e non un'interfaccia) se la classe è definitivo.

Questa classe

I due byte successivi, il componente di questa classe , sono un indice nell'array del pool di costanti. La costante a cui fa riferimento questa classe , constant_pool [this_class], ha due parti, un tag a un byte e un indice del nome a due byte. Il tag sarà uguale a CONSTANT_Class, un valore che indica che questo elemento contiene informazioni su una classe o interfaccia. Constant_pool [name_index] è una costante stringa contenente il nome della classe o dell'interfaccia.

Il componente di questa classe fornisce un assaggio di come viene utilizzato il pool di costanti. Questa stessa classe è solo un indice nel pool di costanti. Quando una JVM cerca constant_pool [this_class], trova un elemento che si identifica come CONSTANT_Class con il suo tag. La JVM sa che gli elementi CONSTANT_Class hanno sempre un indice a due byte nel pool di costanti, chiamato indice del nome, che segue il tag di un byte. Quindi cerca constant_pool [name_index] per ottenere la stringa contenente il nome della classe o dell'interfaccia.

Super classe

Dopo il componente di questa classe c'è il componente super classe , un altro indice a due byte nel pool di costanti. Constant_pool [super_class] è un elemento CONSTANT_Class che punta al nome della super classe da cui discende questa classe.

Interfacce

Il componente interfaces inizia con un conteggio a due byte del numero di interfacce implementate dalla classe (o interfaccia) definita nel file. Immediatamente dopo c'è un array che contiene un indice nel pool di costanti per ogni interfaccia implementata dalla classe. Ogni interfaccia è rappresentata da un elemento CONSTANT_Class nel pool di costanti che punta al nome dell'interfaccia.

Campi

Il componente dei campi inizia con un conteggio a due byte del numero di campi in questa classe o interfaccia. Un campo è un'istanza o una variabile di classe della classe o dell'interfaccia. Dopo il conteggio c'è un array di strutture a lunghezza variabile, una per ogni campo. Ogni struttura rivela informazioni su un campo come il nome, il tipo e, se si tratta di una variabile finale, il suo valore costante. Alcune informazioni sono contenute nella struttura stessa, mentre altre sono contenute in posizioni costanti della piscina indicate dalla struttura.

Gli unici campi che compaiono nell'elenco sono quelli dichiarati dalla classe o dall'interfaccia definita nel file; nell'elenco non viene visualizzato alcun campo ereditato da super classi o superinterfacce.

Metodi

Il componente dei metodi inizia con un conteggio a due byte del numero di metodi nella classe o nell'interfaccia. Questo conteggio include solo i metodi che sono definiti esplicitamente da questa classe, non i metodi che possono essere ereditati dalle superclassi. A seguire il conteggio dei metodi sono i metodi stessi.

La struttura di ogni metodo contiene diverse informazioni sul metodo, incluso il descrittore del metodo (il suo tipo di ritorno e l'elenco di argomenti), il numero di parole dello stack richieste per le variabili locali del metodo, il numero massimo di parole dello stack richieste per l'operando del metodo stack, una tabella delle eccezioni rilevate dal metodo, la sequenza del bytecode e una tabella dei numeri di riga.

Attributi

Sul retro ci sono gli attributi, che forniscono informazioni generali sulla particolare classe o interfaccia definita dal file. La sezione attributi ha un conteggio a due byte del numero di attributi, seguito dagli attributi stessi. Ad esempio, un attributo è l'attributo del codice sorgente; rivela il nome del file sorgente da cui è stato compilato questo file di classe. Le JVM ignoreranno silenziosamente tutti gli attributi che non riconoscono.

Caricamento: una simulazione di un file di classe che raggiunge la sua destinazione JVM

L'applet seguente simula una JVM che carica un file di classe. Il file di classe caricato nella simulazione è stato generato dal compilatore javac dato il seguente codice sorgente Java:

class Act {public static void doMathForever () {int i = 0; mentre (vero) {i + = 1; i * = 2; }}}

Lo snippet di codice sopra riportato proviene dall'articolo del mese scorso sulla JVM. È lo stesso metodo doMathForever () eseguito dall'applet EternalMath dall'articolo del mese scorso. Ho scelto questo codice per fornire un esempio reale non troppo complesso. Sebbene il codice possa non essere molto utile nel mondo reale, viene compilato in un file di classe reale, che viene caricato dalla simulazione seguente.

L'applet GettingLoaded ti consente di guidare la simulazione del carico di classe un passo alla volta. Per ogni passaggio lungo il percorso è possibile leggere il blocco di byte successivo che sta per essere consumato e interpretato dalla JVM. Basta premere il pulsante "Step" per fare in modo che la JVM utilizzi il blocco successivo. Premendo "Indietro" si annullerà il passaggio precedente e premendo "Ripristina" la simulazione tornerà al suo stato originale, consentendo di ricominciare dall'inizio.

La JVM viene mostrata in basso a sinistra consumando il flusso di byte che compone il file di classe Act.class. I byte vengono visualizzati in esadecimale in streaming da un server in basso a destra. I byte viaggiano da destra a sinistra, tra il server e la JVM, un blocco alla volta. La porzione di byte che deve essere consumata dalla JVM alla successiva pressione del pulsante "Step" viene visualizzata in rosso. Questi byte evidenziati sono descritti nella grande area di testo sopra la JVM. Eventuali byte rimanenti oltre il blocco successivo vengono visualizzati in nero.

Ho provato a spiegare completamente ogni blocco di byte nell'area di testo. Ci sono molti dettagli, quindi, nell'area di testo e potresti voler scorrere prima tutti i passaggi per avere un'idea generale, quindi guardare indietro per ulteriori dettagli.

Buon clic.

È necessario un browser abilitato per Java per visualizzare questa applet.

Fare clic qui per il codice sorgente di GettingLoaded. Per eseguire questa applet da soli, avrete anche bisogno dei due file che questa applet recupera dal server, il file ASCII che contiene il testo per ogni passaggio e il file Act.class stesso. Fare clic qui per il codice sorgente dell'applet audio Flying Class Files.

NOTA FINALE: In caratteri piccoli: "The Java Class File Lifestyle" Articolo Copyright (c) 1996 Bill Venners. Tutti i diritti riservati. Applet "GettingLoaded" Copyright (c) 1996 Artima Software Company. Tutti i diritti riservati.

: END_ENDNOTE

Bill Venners è presidente di Artima Software Company. Attraverso Artima, si occupa di sviluppo e consulenza di software personalizzato.

Ulteriori informazioni su questo argomento

  • The Java Virtual Machine Specification, la parola ufficiale di Sun.

    //java.sun.com/1.0alpha3/doc/vmspec/vmspec_1.html

  • Quando uscirà, il libro The Java Virtual Machine Specification , //www.aw.com/cp/lindholm-yellin.html, di Tim Lindholm e Frank Yellin (ISBN 0-201-63452-X), parte di The Java Series, //www.aw.com/cp/javaseries.html), da Addison-Wesley, sarà probabilmente la migliore risorsa JVM.
  • Una bozza del capitolo 4 delle specifiche della Java Virtual Machine , che descrive il formato del file di classe e il verificatore del bytecode, può essere recuperata da JavaSoft.

    //java.sun.com/java.sun.com/newdocs.html

Questa storia, "The Java class file lifestyle" è stata originariamente pubblicata da JavaWorld.