Lorsque vous utilisez le 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 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? La différence n'est-elle possible qu'en théorie ou est-ce que javac
d'Oracle produit réellement des fichiers de classe différents pour les mêmes options d'entrée et de compilation?
pdate 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 diverses plates-formes.
pdate 2 Par 'Same JDK', j'entends également le même javac
exécutable.
Mise à jour Distinction entre la différence théorique et la différence pratique dans les compilateurs Oracle.
[EDIT, ajout d'une question paraphrasée]
"Dans quelles circonstances le même exécutable javac, lorsqu'il est exécuté sur une plateforme différente, produira-t-il un bytecode différent?"
Disons-le de cette façon:
Je peux facilement produire un compilateur Java compilateur entièrement conforme qui ne produit jamais le même .class
fichier deux fois, étant donné le même .Java
fichier.
Je pourrais le faire en peaufinant 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 ne fait pas nécessite le compilateur pour produire des fichiers de classe identiques octet par octet, je voudrais éviter de dépendre un tel résultat.
Cependant, les quelques fois que j'ai vérifié, compiler le même fichier source avec le même compilateur avec les mêmes commutateurs (et les mêmes bibliothèques!) l'a fait entraîner le même .class
des dossiers.
Mise à jour: J'ai récemment trébuché cet article de blog intéressant sur l'implémentation de switch
sur String
dans Java 7 . Dans cet article de blog, il y a quelques parties pertinentes, que je citerai ici (c'est moi qui souligne):
Afin de rendre la sortie du compilateur prévisible et reproductible, les mappages et ensembles utilisés dans ces structures de données sont
LinkedHashMap
s etLinkedHashSet
s plutôt que simplementHashMaps
etHashSets
. En termes d'exactitude fonctionnelle du code généré pendant une compilation donnée, en utilisantHashMap
etHashSet
serait bien ; l'ordre d'itération n'a pas d'importance. Cependant, nous trouvons avantageux que la sortie dejavac
ne varie pas en fonction des détails d'implémentation des classes système.
Cela illustre assez clairement le problème: le compilateur est pas obligatoire pour 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 de essayer (à condition que ce ne soit pas trop cher, probablement).
Il n'y a aucune obligation pour les compilateurs de produire le même bytecode sur chaque plate-forme. Vous devriez consulter l'utilitaire javac
des différents fournisseurs pour avoir une réponse spécifique.
Je vais montrer un exemple pratique pour cela avec la commande de fichiers.
Disons que nous avons 2 fichiers jar: my1.jar
et My2.jar
. Ils sont placés côte à côte dans le répertoire lib
. Le compilateur les lit par ordre alphabétique (puisque c'est lib
), mais l'ordre est 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.
Le my1.jar
a une classe A.class
avec une méthode
public class A {
public static void a(String s) {}
}
Le My2.jar
a 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. Donc, en fonction de la sensibilité à la casse de votre système de fichiers, vous obtiendrez une classe différente en conséquence.
Réponse courte - [~ # ~] non [~ # ~]
Ils bytecode
n'ont pas besoin d'être les mêmes pour une plateforme différente. C'est le JRE (Java Runtime Environment) qui sait exactement comment exécuter le bytecode.
Si vous passez par la 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
version_mineure, version_mineure
Les valeurs des éléments minor_version et major_version sont les numéros de version mineurs et majeurs 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 mineur m, nous désignons la version de son format de fichier de classe comme M.m. Ainsi, les versions de format de fichier de classe peuvent être commandées lexicographiquement, par exemple, 1,5 <2,0 <2,1. A Java Java peut prendre en charge un format de fichier de classe de la 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 a Java implémentation de machine virtuelle 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 La mise en œuvre de la machine virtuelle Java Java de la version 1.0.2 de Sun JDK prend en charge les versions de format de fichier de classe 45.0 à 45.3 inclus. Les versions JDK de Sun 1.1.X peuvent prendre en charge les formats de fichier de classe des versions de la gamme 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 46.0 inclus.
Ainsi, étudier tout cela montre que les fichiers de classe générés sur différentes plateformes n'ont pas besoin d'être identiques.
Premièrement, il n'y a absolument aucune garantie de ce genre dans la spécification. Un compilateur conforme pourrait marquer 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 octet sur chaque build, et de manière triviale.
Deuxièmement, même sans ces astuces désagréables, 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 fait décrit le nom du fichier source comme l'un des attributs standard, et l'ajout de lignes vides au fichier source pourrait bien changer la table des numéros 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 était sur le chemin de classe). Le code qui varie 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 de code natif à partir du bytecode se produit après le chargement de la classe.
Quatrièmement (et surtout) il sent une mauvaise odeur de processus (comme une odeur de code, mais pour la façon dont vous agissez sur le code) de vouloir savoir cela. Versionnez la source si possible, pas la build, et si vous avez besoin de versionner la build, 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.
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 informatique et le système d'exploitation utilisés. La production du code d'octets est effectuée par le compilateur Java, qui utilise un algorithme déterministe pour "transformer" le code source en code d'octets. Ainsi, la sortie sera toujours la même. Dans ces conditions, seule une mise à jour du code source affectera la sortie.
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 prise en charge de la langue japonaise. Pensez aux caractères multi-octets; sauf si le compilateur suppose toujours qu'il doit prendre en charge toutes les langues, il peut être optimisé pour l'ASCII 8 bits.
Il y a une section sur la compatibilité binaire dans Java Language Specification .
Dans le cadre de la compatibilité binaire Release-to-Release dans SOM (Forman, Conner, Danforth et Raper, Proceedings of OOPSLA '95), Java sont compatibles binaires sous toutes les transformations pertinentes que les auteurs identifient (avec quelques mises en garde concernant l'ajout de variables d'instance). En utilisant leur schéma, voici une liste de quelques modifications binaires compatibles importantes que le langage de programmation Java Java prend en charge:
• 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 ont précédemment levé des exceptions qui ne devraient normalement pas se produire ou échoué 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 de champs privés, de méthodes ou de 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 du package.
• Réorganisation des champs, des méthodes ou des constructeurs dans une déclaration de type existante.
• Déplacer une méthode vers le haut dans la hiérarchie des classes.
• Réorganisation de la liste des superinterfaces directes d'une classe ou d'une interface.
• Insertion de nouveaux types de classe ou d'interface dans la hiérarchie de types.
Ce chapitre spécifie les normes minimales de compatibilité binaire garanties par toutes les implémentations. Le langage de programmation Java Java garantit la compatibilité lorsque des binaires de classes et d'interfaces 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 sommes discussion sur 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).
Java allows you write/compile code on one platform and run on different platform.
[~ # ~] afaik [~ # ~] ; cela ne sera possible que lorsque le fichier de classe généré sur une plate-forme différente est identique ou techniquement identique, c'est-à-dire identique.
Modifier
Ce que je veux dire par techniquement le même commentaire, c'est cela. 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.
Pour la question:
"Dans quelles circonstances le même exécutable javac, lorsqu'il est exécuté sur une plate-forme différente, produira-t-il un bytecode différent?"
exemple de compilation croisée montre comment nous pouvons utiliser l'option Javac: -version cible
Cet indicateur génère des fichiers de classe qui sont compatibles avec la version Java que nous spécifions lors de l'appel de cette commande. Par conséquent, les fichiers de classe varient en fonction des attributs que nous fournissons lors du calcul à l'aide de cette option.
Très probablement, la réponse est "oui", mais pour avoir une réponse précise, il faut rechercher des clés ou générer des guides pendant 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.
P.S. JNI peut également avoir son importance.
P.P.S. J'ai trouvé que javac
est lui-même écrit en Java. Cela signifie qu'il est identique sur différentes plateformes. Par conséquent, il ne générerait pas de code différent sans raison. Ainsi, il ne peut le faire qu'avec des appels natifs.
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 y a peut être. 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 existant actuellement produisait le même code d'octet en toutes circonstances (matériel différent, etc.), la réponse de 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'octets 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.
Je dirais autrement.
Tout d'abord, je pense que la question n'est pas d'être déterministe:
Bien sûr, il est déterministe: l'aléatoire est difficile à réaliser en informatique, et il n'y a aucune raison qu'un compilateur l'introduise ici pour une raison quelconque.
Deuxièmement, si vous le reformulez par "dans quelle mesure les fichiers de bytecode sont-ils similaires pour un même fichier de code source?", Alors Non , vous ne pouvez pas compter sur le 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 des changements entre les fichiers .pyc, lorsqu'aucune modification n'a été apportée au fichier .py (et .pyc recompilé de toute façon).
C'est du moins ce que j'ai observé. Mettez donc * .pyc et * .class dans votre .gitignore!