La création de fichiers de classe Java est-elle déterministe?

94

Lorsque vous utilisez le même JDK (c'est-à-dire le même javacexécutable), les fichiers de classe générés sont-ils toujours identiques? Peut-il y avoir une différence selon le système d'exploitation ou le matériel ? À l'exception de la version JDK, pourrait-il y avoir d'autres facteurs entraînant des différences? Existe-t-il des options de compilation pour éviter les différences? Une différence est-elle seulement possible en théorie ou Oracle produit-il javacréellement des fichiers de classe différents pour les mêmes options d'entrée et de compilation?

Mise à jour 1 Je suis intéressé par la génération , c'est-à-dire la sortie du compilateur, pas si un fichier de classe peut être exécuté sur différentes plates-formes.

Mise à jour 2 Par «même JDK», j'entends aussi le même javacexécutable.

Mise à jour 3 Distinction entre différence théorique et différence pratique dans les compilateurs d'Oracle.

[EDIT, ajoutant une question paraphrasée]
"Dans quelles circonstances le même exécutable javac, lorsqu'il est exécuté sur une plate-forme différente, produira un bytecode différent?"

mstrap
la source
5
@Gamb CORA ne signifie pas que le code d'octet sera exactement le même s'il est compilé sur différentes plates-formes; tout cela signifie que le code d'octet généré fera exactement la même chose.
dasblinkenlight
10
Qu'est-ce que tu en as à faire? Cela sent comme un problème XY .
Joachim Sauer
4
@JoachimSauer Considérez si vous contrôlez la version de vos binaires - vous voudrez peut-être détecter les changements uniquement si le code source avait changé, mais vous sauriez que ce n'était pas une idée sensée si le JDK pouvait modifier arbitrairement les binaires de sortie.
RB.
7
@RB .: le compilateur est autorisé à produire tout code d'octet conforme qui représente le code compilé. En fait, certaines mises à jour du compilateur corrigent des bogues qui produisent un code légèrement différent (généralement avec le même comportement d'exécution). En d'autres termes: si vous souhaitez détecter les changements de source, vérifiez les changements de source.
Joachim Sauer
3
@dasblinkenlight: vous supposez que la réponse qu'ils prétendent avoir est en fait correcte et à jour (douteux, étant donné que la question date de 2003).
Joachim Sauer

Réponses:

68

Disons-le de cette façon:

Je peux facilement produire un compilateur Java entièrement conforme qui ne produit jamais .classdeux fois le même fichier, étant donné le même .javafichier.

Je pourrais le faire en modifiant toutes sortes de construction de bytecode ou en ajoutant simplement des attributs superflus à ma méthode (ce qui est autorisé).

Étant donné que la spécification n'exige pas que le compilateur produise des fichiers de classe identiques octet par octet, j'éviterais de dépendre d' un tel résultat.

Cependant , les quelques fois que je l' ai vérifié, la compilation du même fichier source avec le même compilateur avec les mêmes commutateurs (et les mêmes bibliothèques!) Ont fait dans les mêmes .classfichiers.

Mise à jour: Je suis récemment tombé sur cet article de blog intéressant sur l'implémentation d' switchon Stringdans Java 7 . Dans cet article de blog, il y a quelques parties pertinentes, que je vais citer ici (c'est moi qui souligne):

Afin de rendre la sortie du compilateur prévisible et répétable, les cartes et ensembles utilisés dans ces structures de données sont LinkedHashMaps et LinkedHashSets plutôt que juste HashMapset HashSets. En termes d'exactitude fonctionnelle du code généré lors d'une compilation donnée, utiliser HashMapet HashSetserait très bien ; l'ordre d'itération n'a pas d'importance. Cependant, nous trouvons avantageux que javacla sortie de ne varie pas en fonction des détails d'implémentation des classes système .

Cela illustre assez clairement le problème: le compilateur n'est pas obligé d'agir de manière déterministe, tant qu'il correspond à la spécification. Les développeurs du compilateur, cependant, se rendent compte que c'est généralement une bonne idée d' essayer (à condition que ce ne soit pas trop cher, probablement).

Joachim Sauer
la source
@GaborSch que manque-t-il? "Dans quelles circonstances le même exécutable javac, lorsqu'il est exécuté sur une plate-forme différente, produira un bytecode différent?" essentiellement en fonction du caprice du groupe qui a produit le compilateur
emory
3
Eh bien, pour moi, ce serait une raison suffisante pour ne pas en dépendre: un JDK mis à jour pourrait casser mon système de compilation / d'archivage si je dépendais du fait que le compilateur produit toujours le même code.
Joachim Sauer
3
@GaborSch: vous avez déjà un très bel exemple d'une telle situation, donc une vue supplémentaire sur le problème s'imposait. Il n'y a aucun sens à dupliquer votre travail.
Joachim Sauer le
1
@GaborSch Le problème fondamental est que nous voulons implémenter une "mise à jour en ligne" efficace de notre application pour laquelle les utilisateurs ne récupéreraient que les JAR modifiés du site Web. Je peux créer des JAR identiques ayant des fichiers de classe identiques en entrée. Mais la question est de savoir si les fichiers de classe sont toujours identiques lorsqu'ils sont compilés à partir des mêmes fichiers source. Notre concept entier tient et échoue avec ce fait.
mstrap
2
@mstrap: c'est donc un problème XY après tout. Eh bien, vous pouvez examiner les mises à jour différentielles des fichiers jar (de sorte que même des différences d'un octet ne provoqueraient pas le téléchargement de l'ensemble du fichier jar) et vous devriez quand même fournir des numéros de version explicites à vos versions, de sorte que tout ce point est discutable, à mon avis .
Joachim Sauer
38

Il n'y a aucune obligation pour les compilateurs de produire le même bytecode sur chaque plateforme. Vous devriez consulter l' javacutilitaire des différents fournisseurs pour avoir une réponse spécifique.


Je vais montrer un exemple pratique pour cela avec l'ordre des fichiers.

Disons que nous avons 2 fichiers jar: my1.jaret My2.jar. Ils sont placés dans le librépertoire, côte à côte. Le compilateur les lit par ordre alphabétique (puisque c'est le cas lib), mais l'ordre est my1.jar, My2.jarlorsque le système de fichiers est insensible à la casse, et My2.jar, my1.jars'il est sensible à la casse.

A my1.jarune classe A.classavec une méthode

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

Le My2.jara le même A.class, mais avec une signature de méthode différente (accepte Object):

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

Il est clair que si vous avez un appel

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

il compilera un appel de méthode avec une signature différente dans différents cas. Ainsi, en fonction de la sensibilité à la casse de votre système de fichiers, vous obtiendrez une classe différente en conséquence.

gaborsch
la source
1
+1 Il existe une myriade de différences entre le compilateur Eclipse et javac, par exemple la manière dont les constructeurs synthétiques sont générés .
Paul Bellora
2
@GaborSch Je suis intéressé de savoir si le code d'octet est identique pour le même JDK, c'est-à-dire le même javac. Je vais clarifier cela.
mstrap
2
@mstrap J'ai compris votre question, mais la réponse est toujours la même: dépend du fournisseur. Ce javacn'est pas le même, car vous avez différents binaires sur chaque plate-forme (par exemple Win7, Linux, Solaris, Mac). Pour un fournisseur, cela n'a pas de sens d'avoir des implémentations différentes, mais tout problème spécifique à la plate-forme peut influencer le résultat (par exemple, le classement des fichiers dans un répertoire (pensez à votre librépertoire), l'endianness, etc.).
gaborsch
1
Habituellement, la plupart javacsont implémentés en Java (et javacn'est qu'un simple lanceur natif), donc la plupart des différences de plate-forme ne devraient avoir aucun impact.
Joachim Sauer
2
@mstrap - le point qu'il fait valoir est qu'il n'y a aucune obligation pour aucun fournisseur de faire en sorte que son compilateur produise exactement le même bytecode sur toutes les plates-formes, seulement que le bytecode résultant produit les mêmes résultats. Étant donné qu'il n'y a pas de norme / spécification / exigence, la réponse à votre question est "Cela dépend du fournisseur, du compilateur et de la plate-forme spécifiques".
Brian Roach
6

Réponse courte - NON


Longue réponse

Ils bytecoden'ont pas besoin d'être les mêmes pour différentes plates-formes. C'est le JRE (Java Runtime Environment) qui sait exactement comment exécuter le bytecode.

Si vous passez par la spécification Java VM, vous saurez que cela ne doit pas être vrai que le bytecode est le même pour différentes plates-formes.

En passant par le format de fichier de classe , il montre la structure d'un fichier de classe comme

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];
}

Vérification de la version mineure et majeure

minor_version, major_version

Les valeurs des éléments minor_version et major_version sont les numéros de version mineure et majeure de ce fichier de classe. Ensemble, un numéro de version majeur et un numéro de version mineur déterminent la version du format de fichier de classe. Si un fichier de classe a le numéro de version majeur M et le numéro de version mineure m, nous désignons la version de son format de fichier de classe comme Mm Ainsi, les versions de format de fichier de classe peuvent être classées lexicographiquement, par exemple, 1,5 <2,0 <2,1. Une implémentation de machine virtuelle Java peut prendre en charge un format de fichier de classe de version v si et seulement si v se trouve dans une plage contiguë Mi.0 v Mj.m. Seul Sun peut spécifier la plage de versions qu'une implémentation de machine virtuelle Java conforme à un certain niveau de version de la plate-forme Java peut prendre en charge.1

Lire plus à travers les notes de bas de page

1 L'implémentation de machine virtuelle Java de Sun JDK version 1.0.2 prend en charge les versions de format de fichier de classe 45.0 à 45.3 inclus. Les versions 1.1.X du JDK de Sun peuvent prendre en charge les formats de fichiers de classe des versions comprises entre 45.0 et 45.65535 inclus. Les implémentations de la version 1.2 de la plate-forme Java 2 peuvent prendre en charge les formats de fichiers de classe des versions comprises entre 45,0 et 46,0 inclus.

Ainsi, étudier tout cela montre que les fichiers de classe générés sur différentes plates-formes n'ont pas besoin d'être identiques.

mtk
la source
Pouvez-vous donner un lien plus détaillé s'il vous plaît?
mstrap
Je pense que par «plate-forme», ils font référence à la plate-forme Java, pas au système d'exploitation. Bien sûr, lorsque vous demandez à javac 1.7 de créer des fichiers de classe compatibles 1.6, il y aura une différence.
mstrap
@mtk +1 pour afficher le nombre de propriétés générées pour une seule classe lors de la compilation.
gaborsch
3

Premièrement, il n'y a absolument aucune garantie de ce type dans les spécifications. Un compilateur conforme pourrait marquer l'heure de la compilation dans le fichier de classe généré comme un attribut supplémentaire (personnalisé), et le fichier de classe serait toujours correct. Cela produirait cependant un fichier différent au niveau de l'octet sur chaque build, et cela de manière triviale.

Deuxièmement, même sans de telles astuces, il n'y a aucune raison de s'attendre à ce qu'un compilateur fasse exactement la même chose deux fois de suite à moins que sa configuration et son entrée ne soient identiques dans les deux cas. La spécification ne décrit le nom du fichier source comme l' un des attributs standard, et en ajoutant des lignes vides au fichier source pourrait bien changer la table de numéro de ligne.

Troisièmement, je n'ai jamais rencontré de différence de construction en raison de la plate-forme hôte (autre que celle qui était attribuable à des différences dans ce qui se trouvait sur le chemin de classe). Le code qui varierait en fonction de la plate-forme (c'est-à-dire les bibliothèques de code natif) ne fait pas partie du fichier de classe, et la génération réelle du code natif à partir du bytecode se produit après le chargement de la classe.

Quatrièmement (et le plus important) cela sent une mauvaise odeur de processus (comme une odeur de code, mais pour la façon dont vous agissez sur le code) de vouloir le savoir. Version la source si possible, pas la version, et si vous avez besoin de la version de la version, la version au niveau du composant entier et non sur des fichiers de classe individuels. De préférence, utilisez un serveur CI (tel que Jenkins) pour gérer le processus de transformation de la source en code exécutable.

Boursiers Donal
la source
2

Je crois que, si vous utilisez le même JDK, le code d'octet généré sera toujours le même, sans relation avec le matériel et le système d'exploitation utilisés. La production de code d'octet est effectuée par le compilateur java, qui utilise un algorithme déterministe pour "transformer" le code source en code d'octet. Ainsi, la sortie sera toujours la même. Dans ces conditions, seule une mise à jour du code source affectera la sortie.

viniciusjssouza
la source
3
Avez-vous une référence pour cela? Comme je l'ai dit dans les commentaires de la question, ce n'est certainement pas le cas pour C # , j'aimerais donc voir une référence indiquant que c'est le cas pour Java. Je pense en particulier qu'un compilateur multi-thread peut attribuer différents noms d'identifiant sur différentes exécutions.
RB.
1
C'est la réponse à ma question et à ce à quoi je m'attendais, mais je suis d'accord avec RB qu'une référence à ce sujet serait importante.
mstrap
Je crois la même chose. Je ne pense pas que vous trouverez une référence définitive. Si c'est important pour vous, vous pouvez faire une étude. Rassemblez quelques-uns des principaux et essayez-les sur différentes plates-formes en compilant du code open source. Comparez les fichiers d'octets. Publiez le résultat. Assurez-vous de mettre un lien ici.
emory
1

Dans l'ensemble, je dois dire qu'il n'y a aucune garantie que la même source produira le même bytecode lorsqu'elle sera compilée par le même compilateur mais sur une plate-forme différente.

J'examinerais des scénarios impliquant différentes langues (pages de codes), par exemple Windows avec le support de la langue japonaise. Pensez aux caractères multi-octets; sauf si le compilateur suppose toujours qu'il doit prendre en charge tous les langages qu'il peut optimiser pour l'ASCII 8 bits.

Il existe une section sur la compatibilité binaire dans la spécification du langage Java .

Dans le cadre de la compatibilité binaire Release-to-Release dans SOM (Forman, Conner, Danforth et Raper, Proceedings of OOPSLA '95), les binaires du langage de programmation Java sont compatibles binaires sous toutes les transformations pertinentes identifiées par les auteurs (avec quelques mises en garde avec par rapport à l'ajout de variables d'instance). En utilisant leur schéma, voici une liste de quelques modifications importantes compatibles binaires prises en charge par le langage de programmation Java:

• Réimplémentation des méthodes, constructeurs et initialiseurs existants pour améliorer les performances.

• Modification des méthodes ou des constructeurs pour renvoyer des valeurs sur les entrées pour lesquelles ils levaient auparavant des exceptions qui normalement ne devraient pas se produire ou échouaient en entrant dans une boucle infinie ou en provoquant un blocage.

• Ajout de nouveaux champs, méthodes ou constructeurs à une classe ou une interface existante.

• Suppression des champs privés, des méthodes ou des constructeurs d'une classe.

• Lorsqu'un package entier est mis à jour, suppression des champs d'accès par défaut (package uniquement), des méthodes ou des constructeurs de classes et d'interfaces dans le package.

• Réorganiser les champs, méthodes ou constructeurs dans une déclaration de type existante.

• Déplacement d'une méthode vers le haut dans la hiérarchie des classes.

• Réorganiser la liste des super-interfaces directes d'une classe ou d'une interface.

• Insertion de nouveaux types de classe ou d'interface dans la hiérarchie des types.

Ce chapitre spécifie les normes minimales de compatibilité binaire garanties par toutes les implémentations. Le langage de programmation Java garantit la compatibilité lorsque des binaires de classes et d'interfaces sont mélangés qui ne sont pas connus pour provenir de sources compatibles, mais dont les sources ont été modifiées de la manière compatible décrite ici. Notez que nous discutons de la compatibilité entre les versions d'une application. Une discussion sur la compatibilité entre les versions de la plate-forme Java SE dépasse le cadre de ce chapitre.

Kelly S.Français
la source
Cet article explique ce qui peut arriver lorsque nous changeons la version Java. La question de l'OP était ce qui peut arriver si nous changeons de plate-forme dans la même version Java. Sinon, c'est une bonne prise.
gaborsch
1
C'est aussi proche que j'ai pu trouver. Il y a un trou étrange entre la spécification du langage et la spécification de la JVM. Jusqu'à présent, je devrais répondre à l'OP avec «il n'y a aucune garantie que le même compilateur Java produira le même bytecode lorsqu'il est exécuté sur une plate-forme différente.
Kelly
1

Java allows you write/compile code on one platform and run on different platform. AFAIK ; cela ne sera possible que si le fichier de classe généré sur une plate-forme différente est identique ou techniquement identique, c'est-à-dire identique.

Éditer

Ce que je veux dire par techniquement le même commentaire, c'est que. Ils n'ont pas besoin d'être exactement les mêmes si vous comparez octet par octet.

Ainsi, selon les spécifications, le fichier .class d'une classe sur différentes plates-formes n'a pas besoin de correspondre octet par octet.

rai.skumar
la source
La question du PO était de savoir si les fichiers de classe étaient les mêmes ou "techniquement les mêmes".
bdesham
Je suis intéressé de savoir s'ils sont identiques .
mstrap
et la réponse est oui. ce que je veux dire, c'est qu'ils pourraient ne pas être les mêmes si vous comparez octet par octet, c'est pourquoi j'ai utilisé le mot techniquement identique.
rai.skumar
@bdesham il voulait savoir s'ils sont identiques. Je ne sais pas ce que vous avez compris par «techniquement le même» ... est-ce la raison du vote négatif?
rai.skumar
@ rai.skumar Votre réponse dit essentiellement: "Deux compilateurs produiront toujours une sortie qui se comporte de la même manière." Bien sûr, cela est vrai; c'est toute la motivation de la plateforme Java. L'OP voulait savoir si le code émis était octet pour octet identique , ce que vous n'avez pas abordé dans votre réponse.
bdesham
1

Pour la question:

"Dans quelles circonstances le même exécutable javac, lorsqu'il est exécuté sur une plate-forme différente, produira un bytecode différent?"

L' exemple de compilation croisée montre comment nous pouvons utiliser l'option Javac: -target version

Cet indicateur génère des fichiers de classe compatibles avec la version Java que nous spécifions lors de l'appel de cette commande. Par conséquent, les fichiers de classe différeront en fonction des attributs que nous fournissons lors de la comparaison à l'aide de cette option.

PhilipJoseParampettu
la source
0

Très probablement, la réponse est "oui", mais pour avoir une réponse précise, il faut rechercher des clés ou la génération de guid lors de la compilation.

Je ne me souviens pas de la situation où cela se produit. Par exemple, pour avoir un ID à des fins de sérialisation, il est codé en dur, c'est-à-dire généré par le programmeur ou l'IDE.

PS JNI peut également compter.

PPS j'ai trouvé qui javacest lui-même écrit en java. Cela signifie qu'il est identique sur différentes plates-formes. Par conséquent, il ne générerait pas de code différent sans raison. Donc, il ne peut le faire qu'avec des appels natifs.

Suzan Cioc
la source
Notez que Java ne vous protège pas de toutes les différences de plate-forme. L'ordre des fichiers renvoyés lors de la liste du contenu du répertoire n'est pas défini, et cela pourrait avoir un impact sur un compilateur.
Joachim Sauer
0

Il y a deux questions.

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

C'est une question théorique, et la réponse est clairement oui, il peut y en avoir. Comme d'autres l'ont dit, la spécification n'exige pas que le compilateur produise des fichiers de classe identiques octet par octet.

Même si chaque compilateur actuellement existant produisait le même code d'octet en toutes circonstances (matériel différent, etc.), la réponse demain pourrait être différente. Si vous ne prévoyez jamais de mettre à jour javac ou votre système d'exploitation, vous pouvez tester le comportement de cette version dans vos circonstances particulières, mais les résultats peuvent être différents si vous passez, par exemple, de Java 7 Update 11 à Java 7 Update 15.

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

C'est inconnaissable.

Je ne sais pas si la gestion de la configuration est votre raison de poser la question, mais c'est une raison compréhensible de s'en soucier. La comparaison des codes d'octet est un contrôle informatique légitime, mais uniquement pour déterminer si les fichiers de classe ont changé, et non pour déterminer si les fichiers source l'ont fait.

Passer Addison
la source
0

Je le dirais autrement.

Premièrement, je pense que la question n'est pas d'être déterministe:

Bien sûr, il est déterministe: l'aléatoire est difficile à obtenir en informatique, et il n'y a aucune raison pour qu'un compilateur l'introduise ici pour quelque raison que ce soit.

Deuxièmement, si vous le reformulez en "dans quelle mesure les fichiers de bytecode sont-ils similaires pour un même fichier de code source?", Alors non , vous ne pouvez pas vous fier au fait qu'ils seront similaires .

Un bon moyen de s'en assurer est de laisser le .class (ou .pyc dans mon cas) dans votre étape git. Vous vous rendrez compte que parmi les différents ordinateurs de votre équipe, git remarque les changements entre les fichiers .pyc, lorsqu'aucune modification n'a été apportée au fichier .py (et .pyc recompilé de toute façon).

Du moins c'est ce que j'ai observé. Alors mettez * .pyc et * .class dans votre .gitignore!

Augustin Riedinger
la source