94 votes

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

Lors de l'utilisation du même JDK (c'est-à-dire le même javac exécutable), les fichiers de classe générés sont-ils toujours identiques ? Peut-il y avoir une différence en fonction de l'option système d'exploitation o matériel ? Hormis la version du JDK, d'autres facteurs peuvent-ils être à l'origine de ces différences ? Existe-t-il des options de compilation permettant d'éviter les différences ? Une différence n'est-elle possible qu'en théorie ou la solution d'Oracle est-elle la bonne ? javac produit en fait des fichiers de classe différents pour les mêmes options d'entrée et de compilateur ?

Mise à jour 1 Je m'intéresse à la génération Le fichier de classe ne doit pas être considéré comme un fichier de sortie du compilateur, mais comme un fichier de classe qui peut être utilisé comme un fichier de sortie du compilateur. courir sur différentes plateformes.

Mise à jour 2 Par "même JDK", j'entends également le même javac exécutable.

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

[EDIT, ajout de la question paraphrasée].
"Quelles sont les circonstances dans lesquelles le même exécutable javac, lorsqu'il est exécuté sur une plate-forme différente, produira un bytecode différent ?"

68voto

Joachim Sauer Points 133411

Disons les choses comme elles sont :

Je peux facilement produire un compilateur Java entièrement conforme qui ne produit jamais les mêmes .class deux fois, avec le même .java fichier.

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

Étant donné que le spécification n'est pas exiger le compilateur pour produire des fichiers de classe identiques octet par octet, je éviter de dépendre un tel résultat.

Cependant les quelques fois où j'ai vérifié, en compilant le même fichier source avec le même compilateur avec les mêmes commutateurs (et les mêmes bibliothèques !) a fait se traduisent par les mêmes .class des dossiers.

Mise à jour : Je suis récemment tombé sur cet intéressant billet de blog sur la mise en œuvre de la switch en String dans Java 7 . Dans ce billet de blog, il y a quelques passages pertinents que je vais citer ici (c'est moi qui souligne) :

Afin de rendre les résultats du compilateur prévisibles et reproductibles, les cartes et les ensembles utilisés dans ces structures de données sont les suivants LinkedHashMap et LinkedHashSet plutôt que de simples HashMaps y HashSets . En termes de correction fonctionnelle du code généré lors d'une compilation donnée, en utilisant HashMap y HashSet serait très bien l'ordre d'itération n'a pas d'importance. Cependant, l'ordre d'itération n'a pas d'importance, nous estimons qu'il est utile d'avoir javac ne varie pas en fonction des détails de la mise en œuvre des classes de système. .

Ceci illustre assez clairement le problème : Le compilateur est non requis d'agir de manière déterministe, pour autant qu'il corresponde à la spécification. Les développeurs du compilateur, cependant, réalisent qu'il n'est pas possible d'agir de manière déterministe, tant que cela correspond à la spécification. en général une bonne idée pour essayer (à condition que ce ne soit pas trop cher, probablement).

38voto

GaborSch Points 6587

Il n'y a aucune obligation pour les compilateurs de produire le même bytecode sur chaque plateforme. Il est conseillé de consulter les javac pour avoir une réponse spécifique.


Je montrerai un exemple pratique de cette méthode avec la commande de fichiers.

Supposons que nous ayons deux fichiers jar : my1.jar y My2.jar . Ils sont placés dans le lib côte à côte. Le compilateur les lit dans l'ordre alphabétique (puisqu'il s'agit de lib ), mais l'ordre est le suivant my1.jar , My2.jar lorsque le système de fichiers est insensible à la casse, et My2.jar , my1.jar s'il est sensible à la casse.

Les my1.jar a une classe A.class avec une méthode

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

En My2.jar a la 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 selon les cas. Ainsi, en fonction de la sensibilité à la casse de votre système de fichiers, vous obtiendrez une classe différente.

6voto

mtk Points 3982

Réponse courte - NON


Réponse longue

Ils bytecode n'est pas nécessairement la même pour les différentes plates-formes. C'est le JRE (Java Runtime Environment) qui sait exactement comment exécuter le bytecode.

Si vous passez par le Spécification de la VM Java vous saurez que ce n'est pas forcément vrai que le bytecode est le même pour les différentes plateformes.

Passer par le format du fichier de classe Il montre la structure d'un fichier de classe comme suit

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

version_mineure, version_majeure

Les valeurs des éléments minor_version et 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 déterminent la version du format de fichier de classe. Si un fichier de classe a le numéro de version majeure majeur M et un numéro de version mineur m, nous désignons la version de son format de fichier de classe par M.m. M.m. Ainsi, les versions du format de fichier de classe peuvent être classées lexicographiquement, par exemple peuvent être ordonné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 un intervalle contigu Mi.0 v Mj.m. Seul Sun peut spécifier la plage de versions d'une implémentation de machine virtuelle Java conforme à un format de fichier de classe. conforme à un certain niveau de version de la plate-forme Java. plate-forme Java peut prendre en charge.1

Une lecture plus approfondie des notes de bas de page

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

L'examen de tout cela montre donc que les fichiers de classe générés sur différentes plates-formes ne doivent pas nécessairement être identiques.

3voto

Donal Fellows Points 56559

Tout d'abord, les spécifications ne prévoient absolument aucune garantie de ce type. Un compilateur conforme pourrait indiquer l'heure de la compilation dans le fichier de classe généré en tant qu'attribut supplémentaire (personnalisé), et le fichier de classe serait toujours correct. Il produirait cependant un fichier différent au niveau de l'octet à chaque compilation, et ce de manière triviale.

Deuxièmement, même en l'absence 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 ses données d'entrée soient identiques dans les deux cas. La spécification hace décrivent le nom du fichier source comme l'un des attributs standard, et l'ajout de lignes vierges au fichier source pourrait bien modifier la table des numéros de ligne.

Troisièmement, je n'ai jamais rencontré de différence de compilation due à 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 effective du code natif à partir du bytecode a lieu après le chargement de la classe.

Quatrièmement (et surtout), il sent l'odeur d'un mauvais processus (comme une odeur de code, mais pour la façon dont vous agissez sur le code) pour vouloir savoir cela. Versionnez la source si possible, pas le build, et si vous devez versionner le build, versionnez au niveau de l'ensemble du composant et non au niveau des fichiers de classe individuels. De préférence, utilisez un serveur CI (tel que Jenkins) pour gérer le processus de transformation du code source en code exécutable.

2voto

viniciusjssouza Points 107

Je pense que, si vous utilisez le même JDK, le code byte généré sera toujours le même, sans rapport avec le matériel et le système d'exploitation utilisés. La production du code byte est effectuée par le compilateur Java, qui utilise un algorithme déterministe pour "transformer" le code source en code byte. Le résultat sera donc toujours le même. Dans ces conditions, seule une mise à jour du code source affectera le résultat.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X