La creazione di file di classe Java è deterministica?


94

Quando si utilizza lo stesso JDK (ovvero lo stesso javaceseguibile), i file di classe generati sono sempre identici? Può esserci una differenza a seconda del sistema operativo o dell'hardware ? Ad eccezione della versione JDK, potrebbero esserci altri fattori che determinano differenze? Esistono opzioni del compilatore per evitare differenze? C'è una differenza solo in teoria o Oracle javacproduce effettivamente file di classe diversi per le stesse opzioni di input e compilatore?

Aggiornamento 1 Mi interessa la generazione , cioè l'output del compilatore, non se un file di classe può essere eseguito su varie piattaforme.

Aggiornamento 2 Con "Stesso JDK", intendo anche lo stesso javaceseguibile.

Update 3 Distinzione tra differenza teorica e differenza pratica nei compilatori Oracle.

[MODIFICA, aggiunta di una domanda parafrasata]
"Quali sono le circostanze in cui lo stesso eseguibile javac, se eseguito su una piattaforma diversa, produrrà un bytecode diverso?"


5
@Gamb CORA non significa che il codice byte sarà esattamente lo stesso se compilato su piattaforme diverse; tutto ciò che significa è che il codice byte generato farà esattamente la stessa cosa.
dasblinkenlight

10
Perché ti interessi? Questo odora come un problema XY .
Joachim Sauer

4
@JoachimSauer Considera se controlli la versione dei tuoi binari: potresti voler rilevare le modifiche solo se il codice sorgente fosse cambiato, ma sapresti che questa non era un'idea sensata se JDK può modificare arbitrariamente i binari di output.
RB.

7
@RB .: il compilatore è autorizzato a produrre qualsiasi byte code conforme che rappresenta il codice compilato. In effetti, alcuni aggiornamenti del compilatore risolvono bug che producono codice leggermente diverso (di solito con lo stesso comportamento di runtime). In altre parole: se vuoi rilevare le modifiche alla fonte, controlla le modifiche alla fonte.
Joachim Sauer

3
@dasblinkenlight: stai assumendo che la risposta che affermano di avere sia effettivamente corretta e aggiornata (dubbia, dato che la domanda è del 2003).
Joachim Sauer

Risposte:


68

Mettiamola così:

Posso facilmente produrre un compilatore Java completamente conforme che non produce mai lo stesso .classfile due volte, dato lo stesso .javafile.

Potrei farlo modificando tutti i tipi di costruzione di bytecode o semplicemente aggiungendo attributi superflui al mio metodo (cosa consentita).

Dato che la specifica non richiede che il compilatore produca file di classe identici byte per byte, eviterei di dipendere da tale risultato.

Tuttavia , le poche volte che ho controllato, la compilazione dello stesso file sorgente con lo stesso compilatore con le stesse opzioni (e le stesse librerie!) Ha prodotto gli stessi .classfile.

Aggiornamento: di recente mi sono imbattuto in questo interessante post sul blog sull'implementazione di switchon Stringin Java 7 . In questo post del blog, ci sono alcune parti rilevanti, che citerò qui (enfasi mia):

Al fine di rendere l'output del compilatore prevedibile e ripetibile, le mappe e set utilizzati in queste strutture dati sono LinkedHashMaps e LinkedHashSets piuttosto che solo HashMapse HashSets. In termini di correttezza funzionale del codice generato durante una data compilazione, usare HashMape HashSetandrebbe bene ; l'ordine di iterazione non ha importanza. Tuttavia, troviamo vantaggioso che javacl'output di non vari in base ai dettagli di implementazione delle classi di sistema .

Questo illustra abbastanza chiaramente il problema: al compilatore non è richiesto di agire in modo deterministico, purché corrisponda alle specifiche. Gli sviluppatori del compilatore, tuttavia, si rendono conto che in genere è una buona idea provare (a condizione che non sia troppo costoso, probabilmente).


@ GaborSch cosa manca? "Quali sono le circostanze in cui lo stesso eseguibile javac, se eseguito su una piattaforma diversa, produrrà un bytecode diverso?" fondamentalmente a seconda del capriccio del gruppo che ha prodotto il compilatore
emory

3
Bene, per me questo sarebbe un motivo sufficiente per non dipendere da esso: un JDK aggiornato potrebbe rompere il mio sistema di compilazione / archiviazione se dipendessi dal fatto che il compilatore produce sempre lo stesso codice.
Joachim Sauer

3
@ GaborSch: hai già un ottimo esempio di una situazione del genere, quindi era necessario un ulteriore punto di vista sul problema. Non ha senso duplicare il tuo lavoro.
Joachim Sauer

1
@GaborSch Il problema principale è che vogliamo implementare un efficiente "aggiornamento in linea" della nostra applicazione per il quale gli utenti recupererebbero solo i JAR modificati dal sito web. Posso creare JAR identici con file di classe identici come input. Ma la domanda è se i file di classe sono sempre identici quando vengono compilati dagli stessi file sorgente. Il nostro intero concetto sta e fallisce con questo fatto.
mstrap

2
@mstrap: quindi dopo tutto è un problema XY. Bene, puoi esaminare gli aggiornamenti differenziali dei jar (quindi anche le differenze di un byte non causerebbero il download dell'intero jar) e dovresti comunque fornire numeri di versione espliciti alle tue versioni, quindi l'intero punto è discutibile, secondo me .
Joachim Sauer

38

Non vi è alcun obbligo per i compilatori di produrre lo stesso bytecode su ciascuna piattaforma. Dovresti consultare l' javacutilità dei diversi fornitori per avere una risposta specifica.


Mostrerò un esempio pratico per questo con l'ordinamento dei file.

Diciamo che abbiamo 2 file jar: my1.jare My2.jar. Vengono inseriti nella libdirectory, fianco a fianco. Il compilatore le legge in ordine alfabetico (poiché questo è lib), ma l'ordine è my1.jar, My2.jarquando il file system non fa distinzione tra maiuscole e minuscole e My2.jar, my1.jarse distingue tra maiuscole e minuscole.

L' my1.jarha una classe A.classcon un metodo

public class A {
     public static void a(String s) {}
}

Lo My2.jarha lo stesso A.class, ma con diversa firma del metodo (accetta Object):

public class A {
     public static void a(Object o) {}
}

È chiaro che se hai una chiamata

String s = "x"; 
A.a(s); 

compilerà una chiamata al metodo con una firma diversa in casi diversi. Quindi, a seconda della sensibilità al maiuscolo / minuscolo del tuo filesystem, otterrai come risultato una classe diversa.


1
+1 Ci sono una miriade di differenze tra il compilatore Eclipse e javac, ad esempio come vengono generati i costruttori sintetici .
Paul Bellora

2
@GaborSch Mi interessa sapere se il codice byte è identico per lo stesso JDK, cioè lo stesso javac. Lo renderò più chiaro.
mstrap

2
@mstrap ho capito la tua domanda, ma la risposta è sempre la stessa: dipende dal venditore. Non javacè la stessa, perché hai diversi binari su ogni piattaforma (ad esempio Win7, Linux, Solaris, Mac). Per un fornitore, non ha senso avere implementazioni diverse, ma qualsiasi problema specifico della piattaforma può influenzare il risultato (ad es. L'ordinamento del flie in una directory (pensa alla libdirectory), endianness, ecc.).
gaborsch

1
Di solito, la maggior parte javacè implementata in Java (ed javacè solo un semplice launcher nativo), quindi la maggior parte delle differenze di piattaforma non dovrebbe avere alcun impatto.
Joachim Sauer

2
@mstrap - il punto che sta sottolineando è che non è necessario che alcun fornitore faccia in modo che il proprio compilatore produca esattamente lo stesso bytecode su tutte le piattaforme, solo che il bytecode risultante produce gli stessi risultati. Dato che non ci sono standard / specifiche / requisiti, la risposta alla tua domanda è "Dipende dal fornitore, dal compilatore e dalla piattaforma specifici".
Brian Roach

6

Risposta breve - NO


Risposta lunga

Non è bytecodenecessario che siano gli stessi per piattaforme diverse. È il JRE (Java Runtime Environment) che sa esattamente come eseguire il bytecode.

Se segui la specifica Java VM , verrai a sapere che non è necessario che il bytecode sia lo stesso per piattaforme diverse.

Passando attraverso il formato del file di classe , mostra la struttura di un file di classe come

ClassFile {
    u4 magic;
    u2 minor_version;
    u2 major_version;
    u2 constant_pool_count;
    cp_info constant_pool[constant_pool_count-1];
    u2 access_flags;
    u2 this_class;
    u2 super_class;
    u2 interfaces_count;
    u2 interfaces[interfaces_count];
    u2 fields_count;
    field_info fields[fields_count];
    u2 methods_count;
    method_info methods[methods_count];
    u2 attributes_count;
    attribute_info attributes[attributes_count];
}

Controllo della versione minore e principale

minor_version, major_version

I valori degli elementi minor_version e major_version sono i numeri di versione minore e maggiore di questo file di classe. Insieme, un numero di versione maggiore e uno secondario determinano la versione del formato del file di classe. Se un file di classe ha il numero di versione principale M e il numero di versione minore m, indichiamo la versione del suo formato di file di classe come Mm Pertanto, le versioni del formato di file di classe possono essere ordinate lessicograficamente, ad esempio 1.5 <2.0 <2.1. Un'implementazione di Java virtual machine può supportare un formato di file di classe della versione v se e solo se v si trova in un intervallo contiguo Mi.0 v Mj.m. Solo Sun può specificare quale intervallo di versioni può supportare un'implementazione di Java virtual machine conforme a un determinato livello di rilascio della piattaforma Java

Leggendo di più attraverso le note a piè di pagina

1 L'implementazione della Java virtual machine della versione 1.0.2 di JDK di Sun supporta le versioni del formato file di classe dalla 45.0 alla 45.3 inclusa. Le versioni JDK di Sun 1.1.X possono supportare formati di file di classe delle versioni comprese tra 45.0 e 45.65535 incluse. Le implementazioni della versione 1.2 della piattaforma Java 2 possono supportare formati di file di classe delle versioni comprese tra 45.0 e 46.0 incluse.

Quindi, indagare su tutto ciò mostra che i file di classe generati su piattaforme diverse non devono essere identici.


Puoi fornire un link più dettagliato per favore?
mstrap

Penso che per "piattaforma" si riferiscano alla piattaforma Java, non al sistema operativo. Ovviamente, quando si istruisce javac 1.7 a creare file di classe compatibili con 1.6, ci sarà una differenza.
mstrap

@mtk +1 per mostrare quante proprietà vengono generate per una singola classe durante la compilazione.
gaborsch

3

In primo luogo, non esiste assolutamente alcuna garanzia del genere nelle specifiche. Un compilatore conforme potrebbe indicare l'ora della compilazione nel file di classe generato come attributo aggiuntivo (personalizzato) e il file di classe sarebbe comunque corretto. Tuttavia, produrrebbe un file diverso a livello di byte su ogni singola build, e banalmente.

In secondo luogo, anche senza questi fastidiosi trucchi, non c'è motivo di aspettarsi che un compilatore faccia esattamente la stessa cosa due volte di seguito a meno che sia la sua configurazione che il suo input non siano identici nei due casi. La spec non descrivono il nome del file di origine come uno degli attributi standard, e l'aggiunta di righe vuote al file di origine potrebbe cambiare la tabella di numero di riga.

In terzo luogo, non ho mai riscontrato differenze nella build a causa della piattaforma host (a parte quella attribuibile alle differenze in ciò che era sul classpath). Il codice che varia in base alla piattaforma (cioè, le librerie di codice nativo) non fa parte del file di classe e la generazione effettiva del codice nativo dal bytecode avviene dopo il caricamento della classe.

In quarto luogo (e soprattutto) puzza di un cattivo odore di processo (come l' odore di un codice, ma per come si agisce sul codice) volerlo sapere. Esegui la versione del codice sorgente, se possibile, non della build e, se è necessario eseguire la versione della build, versione a livello di intero componente e non sui singoli file di classe. Preferibilmente, usa un server CI (come Jenkins) per gestire il processo di trasformazione della sorgente in codice eseguibile.


2

Credo che, se si utilizza lo stesso JDK, il byte code generato sarà sempre lo stesso, senza relazione con l'harware e il sistema operativo utilizzato. La produzione del codice byte viene eseguita dal compilatore java, che utilizza un algoritmo deterministico per "trasformare" il codice sorgente in codice byte. Quindi, l'output sarà sempre lo stesso. In queste condizioni, solo un aggiornamento del codice sorgente influenzerà l'output.


3
Hai un riferimento per questo però? Come ho già detto nei commenti questione, questo è sicuramente non è il caso di C # , quindi mi piacerebbe vedere un riferimento affermando che è il caso per Java. In particolare, penso che un compilatore multi-thread potrebbe assegnare nomi di identificatori diversi su esecuzioni diverse.
RB.

1
Questa è la risposta alla mia domanda e quello che mi aspetterei, tuttavia sono d'accordo con RB sul fatto che un riferimento per questo sarebbe importante.
mstrap

Credo lo stesso. Non credo che troverai un riferimento definitivo. Se è importante per te, puoi fare uno studio. Raccogli alcuni dei migliori e provali su diverse piattaforme compilando del codice open source. Confronta i file di byte. Pubblica il risultato. Assicurati di inserire un link qui.
emory

1

Nel complesso, devo dire che non vi è alcuna garanzia che la stessa sorgente produrrà lo stesso bytecode quando compilata dallo stesso compilatore ma su una piattaforma diversa.

Analizzerei scenari che coinvolgono lingue diverse (code-page), ad esempio Windows con supporto per la lingua giapponese. Pensa a caratteri multibyte; a meno che il compilatore non presuma sempre di dover supportare tutti i linguaggi, potrebbe ottimizzare per ASCII a 8 bit.

C'è una sezione sulla compatibilità binaria nella specifica del linguaggio Java .

Nell'ambito della compatibilità binaria da rilascio a rilascio in SOM (Forman, Conner, Danforth e Raper, Proceedings of OOPSLA '95), i binari del linguaggio di programmazione Java sono compatibili con i binari sotto tutte le trasformazioni rilevanti che gli autori identificano (con alcuni avvertimenti con rispetto all'aggiunta di variabili di istanza). Usando il loro schema, ecco un elenco di alcune importanti modifiche compatibili con i binari che il linguaggio di programmazione Java supporta:

• Reimplementazione di metodi, costruttori e inizializzatori esistenti per migliorare le prestazioni.

• Modifica di metodi o costruttori per restituire valori sugli input per i quali in precedenza generavano eccezioni che normalmente non dovrebbero verificarsi o non sono riuscite entrando in un ciclo infinito o provocando un deadlock.

• Aggiunta di nuovi campi, metodi o costruttori a una classe o interfaccia esistente.

• Eliminazione di campi, metodi o costruttori privati ​​di una classe.

• Quando viene aggiornato un intero pacchetto, l'eliminazione di campi di accesso, metodi o costruttori di classi e interfacce predefiniti (solo pacchetto) nel pacchetto.

• Riordinare i campi, i metodi o i costruttori in una dichiarazione di tipo esistente.

• Spostare un metodo verso l'alto nella gerarchia delle classi.

• Riordinare l'elenco delle superinterfacce dirette di una classe o interfaccia.

• Inserimento di nuovi tipi di classe o interfaccia nella gerarchia dei tipi.

Questo capitolo specifica gli standard minimi per la compatibilità binaria garantita da tutte le implementazioni. Il linguaggio di programmazione Java garantisce la compatibilità quando i binari di classi e interfacce sono misti che non si sa provengono da sorgenti compatibili, ma le cui sorgenti sono state modificate nei modi compatibili qui descritti. Si noti che stiamo discutendo la compatibilità tra le versioni di un'applicazione. Una discussione sulla compatibilità tra le versioni della piattaforma Java SE esula dallo scopo di questo capitolo.


Questo articolo discute cosa può succedere se cambiamo la versione di Java. La domanda dell'OP era cosa può succedere se cambiamo piattaforma all'interno della stessa versione di Java. Altrimenti è una buona presa.
gaborsch

1
È il più vicino che ho potuto trovare. C'è uno strano buco tra le specifiche della lingua e le specifiche della JVM. Finora, dovrei rispondere all'OP con "non vi è alcuna garanzia che lo stesso compilatore Java produrrà lo stesso bytecode se eseguito su una piattaforma diversa".
Kelly S. French il

1

Java allows you write/compile code on one platform and run on different platform. AFAIK ; questo sarà possibile solo quando il file di classe generato su una piattaforma diversa è uguale o tecnicamente uguale, cioè identico.

modificare

Quello che intendo tecnicamente con lo stesso commento è questo. Non è necessario che siano esattamente gli stessi se si confrontano byte per byte.

Quindi, come da specifica, il file .class di una classe su piattaforme diverse non ha bisogno di corrispondere byte per byte.


La domanda dell'OP era se i file di classe fossero gli stessi o "tecnicamente gli stessi".
bdesham

Mi interessa sapere se sono identici .
mstrap

e la risposta è sì. quello che voglio dire è che potrebbero non essere gli stessi se si confrontano byte per byte, ecco perché ho usato la parola tecnicamente uguale.
rai.skumar

@bdesham voleva sapere se sono identici. non sei sicuro di cosa hai capito per "tecnicamente lo stesso" ... è questo il motivo del voto negativo?
rai.skumar

@ rai.skumar La tua risposta fondamentalmente dice: "Due compilatori produrranno sempre un output che si comporta allo stesso modo". Ovviamente questo è vero; è l'intera motivazione della piattaforma Java. L'OP voleva sapere se il codice emesso era identico byte per byte , cosa che non hai indicato nella tua risposta.
bdesham

1

Per la domanda:

"Quali sono le circostanze in cui lo stesso eseguibile javac, se eseguito su una piattaforma diversa, produrrà un bytecode diverso?"

L' esempio di Cross-Compilation mostra come possiamo usare l'opzione Javac: -target version

Questo flag genera file di classe che sono compatibili con la versione Java specificata durante l'invocazione di questo comando. Quindi i file di classe differiranno a seconda degli attributi che forniamo durante la compilazione usando questa opzione.


0

Molto probabilmente, la risposta è "sì", ma per avere una risposta precisa, è necessario cercare alcune chiavi o la generazione di guide durante la compilazione.

Non riesco a ricordare la situazione in cui questo accade. Ad esempio, per avere un ID per scopi di serializzazione, è hardcoded, cioè generato dal programmatore o dall'IDE.

PS Anche JNI può importare.

PPS Ho scoperto che javacè scritto in java. Ciò significa che è identico su piattaforme diverse. Quindi non genererebbe codice diverso senza una ragione. Quindi, può farlo solo con le chiamate native.


Nota che Java non ti protegge da tutte le differenze di piattaforma. L'ordine dei file restituiti quando si elenca il contenuto della directory non è definito e ciò potrebbe plausibilmente avere un certo impatto su un compilatore.
Joachim Sauer

0

Ci sono due domande.

Can there be a difference depending on the operating system or hardware? 

Questa è una domanda teorica e la risposta è chiaramente sì, è possibile esserci. Come altri hanno già detto, la specifica non richiede che il compilatore produca file di classe identici byte per byte.

Anche se ogni compilatore attualmente esistente producesse lo stesso codice byte in tutte le circostanze (hardware diverso, ecc.), La risposta domani potrebbe essere diversa. Se non si prevede mai di aggiornare javac o il sistema operativo, è possibile testare il comportamento di quella versione in circostanze particolari, ma i risultati potrebbero essere diversi se si passa, ad esempio, da Java 7 Update 11 a Java 7 Update 15.

What are the circumstances where the same javac executable, when run on a different platform, will produce different bytecode?

È inconoscibile.

Non so se la gestione della configurazione è il motivo per cui poni la domanda, ma è un motivo comprensibile per preoccuparti. Il confronto dei codici byte è un controllo IT legittimo, ma solo per determinare se i file di classe sono cambiati, non per determinare se i file di origine lo hanno fatto.


0

La metterei in un altro modo.

In primo luogo, penso che la domanda non sia sull'essere deterministici:

Ovviamente è deterministico: la casualità è difficile da ottenere in informatica e non c'è motivo per cui un compilatore la introduca qui per nessun motivo.

Secondo, se lo riformuli in base a "quanto sono simili i file bytecode per uno stesso file codice sorgente?", Allora No , non puoi fare affidamento sul fatto che saranno simili .

Un buon modo per assicurarti di ciò è lasciare il .class (o .pyc nel mio caso) nel tuo stadio git. Ti renderai conto che tra i diversi computer del tuo team, git nota le modifiche tra i file .pyc, quando non sono state apportate modifiche al file .py (e .pyc comunque ricompilato).

Almeno questo è quello che ho osservato. Quindi metti * .pyc e * .class nel tuo .gitignore!

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.