ejecutar - compilar proyecto java desde consola
¿La creación de archivos de clase Java es determinista? (11)
Cuando se utiliza el mismo JDK (es decir, el mismo ejecutable javac
), ¿los archivos de clase generados son siempre idénticos? ¿Puede haber una diferencia según el sistema operativo o el hardware ? Excepto en la versión JDK, ¿podría haber otros factores que pudieran generar diferencias? ¿Hay alguna opción de compilación para evitar diferencias? ¿Es una diferencia solo posible en teoría o el javac
de Oracle realmente produce diferentes archivos de clase para las mismas opciones de entrada y compilación?
Actualización 1 Me interesa la generación , es decir, la salida del compilador, no si un archivo de clase se puede ejecutar en varias plataformas.
Actualización 2 Por ''Mismo JDK'', también me refiero al mismo ejecutable de javac
.
Actualización 3 Distinción entre la diferencia teórica y la diferencia práctica en los compiladores de Oracle.
[EDITAR, añadiendo una pregunta parafraseada]
"¿Cuáles son las circunstancias en las que el mismo ejecutable javac, cuando se ejecuta en una plataforma diferente, producirá un bytecode diferente?"
Creo que, si usa el mismo JDK, el código de bytes generado siempre será el mismo, sin relación con el hardware y el SO utilizados. La producción del código de bytes se realiza mediante el compilador de Java, que utiliza un algoritmo determinista para "transformar" el código fuente en código de bytes. Por lo tanto, la salida siempre será la misma. En estas condiciones, solo una actualización del código fuente afectará la salida.
En general, tendría que decir que no hay garantía de que la misma fuente produzca el mismo bytecode cuando sea compilado por el mismo compilador pero en una plataforma diferente.
Me gustaría ver escenarios que involucran diferentes idiomas (páginas de códigos), por ejemplo, Windows con soporte en japonés. Piensa en caracteres de varios bytes; a menos que el compilador siempre asuma que necesita admitir todos los idiomas, podría optimizar para ASCII de 8 bits.
Hay una sección sobre compatibilidad binaria en la Especificación del lenguaje Java .
En el marco de la Compatibilidad Binaria Release-to-Release en SOM (Forman, Conner, Danforth y Raper, Proceedings of OOPSLA ''95), los binarios del lenguaje de programación Java son binarios compatibles bajo todas las transformaciones relevantes que los autores identifican (con algunas advertencias con respecto a la adición de variables de instancia). Usando su esquema, aquí hay una lista de algunos cambios compatibles binarios importantes que admite el lenguaje de programación Java:
• Reimplementar los métodos, constructores e inicializadores existentes para mejorar el rendimiento.
• Cambiar los métodos o los constructores para devolver valores en las entradas para las cuales previamente lanzaron excepciones que normalmente no deberían ocurrir o fallaron entrando en un bucle infinito o causando un punto muerto.
• Agregar nuevos campos, métodos o constructores a una clase o interfaz existente.
• Eliminar campos privados, métodos o constructores de una clase.
• Cuando se actualiza un paquete completo, elimina los campos de acceso predeterminados (solo paquete), los métodos o los constructores de clases e interfaces en el paquete.
• Reordenando los campos, métodos o constructores en una declaración de tipo existente.
• Mover un método hacia arriba en la jerarquía de clases.
• Reordenando la lista de superinterfaces directas de una clase o interfaz.
• Insertar nueva clase o tipos de interfaz en la jerarquía de tipos.
Este capítulo especifica los estándares mínimos de compatibilidad binaria garantizados por todas las implementaciones. El lenguaje de programación Java garantiza la compatibilidad cuando se mezclan binarios de clases e interfaces que no se sabe que proceden de fuentes compatibles, pero cuyas fuentes se han modificado de las formas compatibles descritas aquí. Tenga en cuenta que estamos discutiendo la compatibilidad entre versiones de una aplicación. Una discusión sobre la compatibilidad entre lanzamientos de la plataforma Java SE está más allá del alcance de este capítulo.
En primer lugar, no hay absolutamente ninguna garantía en la especificación. Un compilador conforme podría marcar el tiempo de compilación en el archivo de clase generado como un atributo adicional (personalizado), y el archivo de clase aún sería correcto. Sin embargo, produciría un archivo diferente de nivel de bytes en cada compilación, y trivialmente.
En segundo lugar, incluso sin esos trucos desagradables, no hay razón para esperar que un compilador haga exactamente lo mismo dos veces seguidas a menos que tanto su configuración como su entrada sean idénticas en los dos casos. La especificación describe el nombre del archivo fuente como uno de los atributos estándar, y agregar líneas en blanco al archivo fuente podría cambiar la tabla de números de línea.
En tercer lugar, nunca he encontrado ninguna diferencia en la compilación debido a la plataforma de host (que no sea la que fue atribuible a las diferencias en lo que estaba en el classpath). El código que varía en función de la plataforma (es decir, bibliotecas de códigos nativos) no es parte del archivo de clase, y la generación real del código nativo del bytecode ocurre después de que se carga la clase.
En cuarto lugar (y lo que es más importante) apesta a un mal olor de proceso (como un olor a código, pero por la forma en que actúa sobre el código) para querer saber esto. Actualice la fuente si es posible, no la compilación, y si necesita versionar la compilación, versión en el nivel de componente completo y no en archivos de clase individuales. De preferencia, use un servidor de CI (como Jenkins) para administrar el proceso de convertir la fuente en código ejecutable.
Hay dos preguntas
Can there be a difference depending on the operating system or hardware?
Esta es una pregunta teórica, y la respuesta es claramente, sí, puede haber. Como han dicho otros, la especificación no requiere que el compilador produzca archivos de clase idénticos byte por byte.
Incluso si cada compilador actualmente existente produce el mismo código de bytes en todas las circunstancias (hardware diferente, etc.), la respuesta de mañana podría ser diferente. Si nunca planea actualizar javac o su sistema operativo, puede probar el comportamiento de esa versión en sus circunstancias particulares, pero los resultados pueden ser diferentes si va desde, por ejemplo, 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?
Eso es incognoscible.
No sé si la gestión de la configuración es su razón para hacer la pregunta, pero es una razón comprensible para preocuparse. La comparación de códigos de bytes es un control de TI legítimo, pero solo para determinar si los archivos de clase cambiaron, no para determinar si los archivos de origen sí lo hicieron.
Lo más probable es que la respuesta sea "sí", pero para tener una respuesta precisa, uno necesita buscar algunas claves o generación de guía durante la compilación.
No recuerdo la situación en que esto ocurre. Por ejemplo, para tener ID para propósitos de serialización está codificado, es decir, generado por el programador o IDE.
PS También JNI puede importar.
PPS Descubrí que javac
está escrito en java. Esto significa que es idéntico en diferentes plataformas. Por lo tanto, no generaría código diferente sin una razón. Por lo tanto, puede hacer esto solo con llamadas nativas.
No existe obligación para los compiladores de producir el mismo código de bytes en cada plataforma. Debe consultar la utilidad javac
los diferentes proveedores para obtener una respuesta específica.
Mostraré un ejemplo práctico para esto con el orden de archivos.
Digamos que tenemos 2 archivos jar: my1.jar
y My2.jar
. Se colocan en el directorio lib
, uno al lado del otro. El compilador los lee en orden alfabético (ya que es lib
), pero el orden es my1.jar
, My2.jar
cuando el sistema de archivos no distingue entre mayúsculas y minúsculas, y My2.jar
, my1.jar
si es sensible a mayúsculas y minúsculas.
El my1.jar
tiene una clase A.class
con un método
public class A {
public static void a(String s) {}
}
My2.jar
tiene la misma A.class
, pero con una firma de método diferente (acepta Object
):
public class A {
public static void a(Object o) {}
}
Está claro que si tienes una llamada
String s = "x";
A.a(s);
compilará una llamada a método con una firma diferente en diferentes casos. Entonces, dependiendo de la sensibilidad a las mayúsculas y minúsculas de su sistema de archivos, obtendrá como resultado una clase diferente.
Para la pregunta:
"¿Cuáles son las circunstancias en las que el mismo ejecutable javac, cuando se ejecuta en una plataforma diferente, producirá un bytecode diferente?"
El ejemplo de compilación cruzada muestra cómo podemos usar la opción Javac: -target versión
Este indicador genera archivos de clase que son compatibles con la versión de Java que especificamos al invocar este comando. Por lo tanto, los archivos de clase serán diferentes según los atributos que proporcionamos durante la comparación utilizando esta opción.
Pongámoslo de esta manera:
Puedo producir fácilmente un compilador Java totalmente conformes que nunca produce el mismo archivo .class
dos veces, dado el mismo archivo .java
.
Podría hacer esto modificando todo tipo de construcción de código de bytes o simplemente agregando atributos superfluos a mi método (que está permitido).
Dado que la especificación no requiere que el compilador produzca archivos de clase idénticos de byte por byte, evitaría tal resultado.
Sin embargo , las pocas veces que he comprobado, compilar el mismo archivo fuente con el mismo compilador con los mismos modificadores (¡y las mismas bibliotecas!) Resultó en los mismos archivos .class
.
Actualización: Recientemente me encontré con esta interesante entrada de blog sobre la implementación de switch
on String
en Java 7 . En esta publicación del blog, hay algunas partes relevantes, que citaré aquí (énfasis mío):
Para que la salida del compilador sea predecible y repetible, los mapas y conjuntos utilizados en estas estructuras de datos son
LinkedHashMap
yLinkedHashSet
s en lugar de soloHashMaps
yHashSets
. En términos de corrección funcional del código generado durante una compilación dada, usarHashMap
yHashSet
estaría bien ; el orden de iteración no importa. Sin embargo, nos parece beneficioso que la salida dejavac
no varíe en función de los detalles de implementación de las clases del sistema .
Esto ilustra claramente el problema: no se requiere que el compilador actúe de manera determinista, siempre que coincida con la especificación. Los desarrolladores de compiladores, sin embargo, se dan cuenta de que generalmente es una buena idea intentarlo (siempre que no sea demasiado caro, probablemente).
Respuesta corta - NO
Respuesta larga
No es necesario que el bytecode
sea el mismo para una plataforma diferente. Es el JRE (Java Runtime Environment) el que sabe exactamente cómo ejecutar el bytecode.
Si revisas la especificación Java VM , sabrás que no es cierto que el bytecode sea el mismo para las diferentes plataformas.
Al pasar por el formato de archivo de clase , muestra la estructura de un archivo de clase como
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];
}
Comprobación de la versión menor y mayor
versión_de_menor, versión_principal
Los valores de los elementos minor_version e major_version son los números de versión menor y principal de este archivo de clase. En conjunto, un número de versión principal y uno menor determinan la versión del formato del archivo de clase. Si un archivo de clase tiene un número de versión principal M y un número de versión menor m, denotamos la versión de su formato de archivo de clase como Mm. Por lo tanto, las versiones de formato de archivo de clase pueden ordenarse lexicográficamente, por ejemplo, 1.5 <2.0 <2.1. Una implementación de máquina virtual Java puede admitir un formato de archivo de clase de la versión v si y solo si v se encuentra en algún rango contiguo Mi.0 v Mj.m. Solo Sun puede especificar el rango de versiones que puede admitir una implementación de máquina virtual Java conforme a un determinado nivel de versión de la plataforma Java.1
Leer más a través de las notas al pie
1 La implementación de la máquina virtual Java de Sun''s JDK versión 1.0.2 admite las versiones de formato de archivo de clase 45.0 a 45.3 inclusive. Las versiones JDK 1.1.X de Sun pueden admitir formatos de archivos de clase de versiones en el rango de 45.0 a 45.65535 inclusive. Las implementaciones de la versión 1.2 de la plataforma Java 2 pueden admitir formatos de archivos de clase de versiones en el rango de 45.0 a 46.0 inclusive.
Entonces, investigar todo esto muestra que los archivos de clase generados en diferentes plataformas no necesitan ser idénticos.
Yo lo pondría de otra manera.
Primero, creo que la pregunta no es sobre ser determinista:
Por supuesto, es determinista: la aleatoriedad es difícil de lograr en informática, y no hay ninguna razón para que un compilador la presente aquí por algún motivo.
En segundo lugar, si lo reformula por "¿qué tan similares son los archivos de código de bytes para un mismo archivo de código fuente?", Entonces no , no puede confiar en el hecho de que serán similares.
Una buena forma de asegurarme de esto es dejar la .class (o .pyc en mi caso) en su etapa de git. Te darás cuenta de que entre las diferentes computadoras de tu equipo, git nota los cambios entre archivos .pyc, cuando no se introdujeron cambios en el archivo .py (y .pyc recompilado de todos modos).
Al menos eso es lo que observé. ¡Ponga * .pyc y * .class en su .gitignore!
Java allows you write/compile code on one platform and run on different platform.
AFAIK ; esto solo será posible cuando el archivo de clase generado en diferentes plataformas sea el mismo o técnicamente idéntico, es decir, idéntico.
Editar
Lo que quiero decir con técnicamente el mismo comentario es eso. No necesitan ser exactamente iguales si compara byte por byte.
Entonces, según la especificación, el archivo .class de una clase en diferentes plataformas no necesita coincidir byte a byte.