¿Es determinista la creación de archivos de clases Java?


94

Cuando se usa el mismo JDK (es decir, el mismo javacejecutable), ¿los archivos de clases generados son siempre idénticos? ¿Puede haber alguna diferencia según el sistema operativo o el hardware ? Excepto en la versión JDK, ¿podría haber otros factores que generen diferencias? ¿Hay opciones de compilador para evitar diferencias? ¿Es posible una diferencia solo en teoría o Oracle javacrealmente produce archivos de clases diferentes para las mismas opciones de entrada y compilador?

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 javacejecutable.

Actualización 3 Distinción entre diferencia teórica y diferencia práctica en los compiladores de Oracle.

[EDITAR, agregando una pregunta parafraseada]
"¿Cuáles son las circunstancias en las que el mismo ejecutable javac, cuando se ejecuta en una plataforma diferente, producirá un código de bytes diferente?"


5
@Gamb CORA no significa que el código de bytes será exactamente el mismo si se compila en diferentes plataformas; todo lo que significa es que el código de bytes generado hará exactamente lo mismo.
dasblinkenlight

10
¿Por qué te importa? Esto huele a problema XY .
Joachim Sauer

4
@JoachimSauer Considere si controla la versión de sus binarios; es posible que desee detectar cambios solo si el código fuente ha cambiado, pero sabría que esta no es una idea sensata si el JDK puede cambiar arbitrariamente los binarios de salida.
RB.

7
@RB .: el compilador puede producir cualquier código de bytes conforme que represente el código compilado. De hecho, algunas actualizaciones del compilador corrigen errores que producen un código ligeramente diferente (generalmente con el mismo comportamiento en tiempo de ejecución). En otras palabras: si desea detectar cambios en la fuente, verifique los cambios en la fuente.
Joachim Sauer

3
@dasblinkenlight: está asumiendo que la respuesta que afirman tener es realmente correcta y actualizada (dudoso, dado que la pregunta es de 2003).
Joachim Sauer

Respuestas:


68

Pongámoslo de esta manera:

Puedo producir fácilmente un compilador de Java totalmente conforme que nunca produce el mismo .classarchivo dos veces, dado el mismo.java archivo.

Podría hacer esto ajustando 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 byte por byte, evitaría depender tal resultado.

Sin embargo , las pocas veces que he comprobado, compilar el mismo archivo fuente con el mismo compilador con los mismos conmutadores (¡y las mismas bibliotecas!) Dieron como resultado el mismo.class archivos.

Actualización: Recientemente me encontré con esta interesante publicación de blog sobre la implementación de switchon Stringen Java 7 . En esta publicación de 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 usados ​​en estas estructuras de datos son LinkedHashMaps y LinkedHashSets en lugar de solo HashMapsy HashSets. En términos de corrección funcional del código generado durante una compilación determinada, usar HashMapy HashSetestaría bien ; el orden de iteración no importa. Sin embargo, nos parece beneficioso que javacla salida no varíe según los detalles de implementación de las clases del sistema .

Esto ilustra bastante claramente el problema: no se requiere que el compilador actúe de manera determinista, siempre que coincida con la especificación. Sin embargo, los desarrolladores del compilador se dan cuenta de que, en general, es una buena idea intentarlo (siempre que no sea demasiado caro, probablemente).


@GaborSch ¿qué le falta? "¿Cuáles son las circunstancias en las que el mismo ejecutable javac, cuando se ejecuta en una plataforma diferente, producirá un código de bytes diferente?" básicamente dependiendo del capricho del grupo que produjo el compilador
emory

3
Bueno, para mí esto sería motivo suficiente para no depender de él: un JDK actualizado podría romper mi sistema de compilación / archivo si dependiera del hecho de que el compilador siempre produce el mismo código.
Joachim Sauer

3
@GaborSch: ya tiene un ejemplo perfectamente bueno de tal situación, por lo que se necesitaba una vista adicional del problema. No tiene sentido duplicar tu trabajo.
Joachim Sauer

1
@GaborSch La raíz del problema es que queremos implementar una "actualización en línea" eficiente de nuestra aplicación para la cual los usuarios solo buscarían archivos JAR modificados del sitio web. Puedo crear JAR idénticos con archivos de clase idénticos como entrada. Pero la pregunta es si los archivos de clase son siempre idénticos cuando se compilan a partir de los mismos archivos fuente. Todo nuestro concepto se sostiene y falla con este hecho.
mstrap

2
@mstrap: entonces es un problema XY después de todo. Bueno, puede buscar actualizaciones diferenciales de jar (por lo que incluso las diferencias de un byte no causarían que se vuelva a descargar todo el jar) y debe proporcionar números de versión explícitos a sus lanzamientos de todos modos, por lo que todo el punto es discutible, en mi opinión .
Joachim Sauer

38

Los compiladores no tienen la obligación de producir el mismo código de bytes en cada plataforma. Debe consultar la javacutilidad de los diferentes proveedores para tener una respuesta específica.


Mostraré un ejemplo práctico de esto con la ordenación de archivos.

Digamos que tenemos 2 archivos jar: my1.jary My2.jar. Se colocan en el libdirectorio, uno al lado del otro. El compilador los lee en orden alfabético (ya que es así lib), pero el orden es my1.jar, My2.jarcuando el sistema de archivos no distingue entre mayúsculas y minúsculas y My2.jar, my1.jarsi es sensible a las mayúsculas y minúsculas.

El my1.jartiene una clase A.classcon un método

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

El My2.jartiene el mismo A.class, pero con diferente firma del método (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 un método con una firma diferente en diferentes casos. Entonces, dependiendo de la sensibilidad de mayúsculas y minúsculas de su sistema de archivos, obtendrá una clase diferente como resultado.


1
+1 Existen innumerables diferencias entre el compilador Eclipse y javac, por ejemplo, cómo se generan los constructores sintéticos .
Paul Bellora

2
@GaborSch Estoy interesado en saber si el código de bytes es idéntico para el mismo JDK, es decir, el mismo javac. Lo dejaré más claro.
mstrap

2
@mstrap Entendí tu pregunta, pero la respuesta sigue siendo la misma: depende del proveedor. No javaces lo mismo, porque tiene diferentes binarios en cada plataforma (por ejemplo, Win7, Linux, Solaris, Mac). Para un proveedor, no tiene sentido tener diferentes implementaciones, pero cualquier problema específico de la plataforma puede influir en el resultado (p. Ej., Ordenar las moscas en un directorio (piense en su libdirectorio), endianness, etc.).
gaborsch

1
Por lo general, la mayor parte javacse implementa en Java (y javaces solo un iniciador nativo simple), por lo que la mayoría de las diferencias de plataforma no deberían tener ningún impacto.
Joachim Sauer

2
@mstrap: el punto que él está haciendo es que no existe ningún requisito para que ningún proveedor haga que su compilador produzca exactamente el mismo código de bytes en todas las plataformas, solo que el código de bytes resultante produce los mismos resultados. Dado que no existe un estándar / especificación / requisito, la respuesta a su pregunta es "Depende del proveedor, compilador y plataforma específicos".
Brian Roach

6

Respuesta corta - NO


Respuesta larga

Ellos bytecode necesario que sean iguales para diferentes plataformas. Es el JRE (Java Runtime Environment) el que sabe exactamente cómo ejecutar el bytecode.

Si pasa por la especificación Java VM , llegará a saber que esto no tiene por qué ser cierto que el código de bytes es el mismo para diferentes plataformas.

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

Comprobando sobre la versión menor y mayor

minor_version, major_version

Los valores de los elementos minor_version y major_version son los números de versión menor y mayor de este archivo de clase. Juntos, un número de versión principal y otro menor determinan la versión del formato de archivo de clase. Si un archivo de clase tiene el número de versión principal M y el número de versión secundaria 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 qué rango de versiones puede admitir una implementación de máquina virtual Java que se ajuste a un cierto nivel de lanzamiento 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 la versión 1.0.2 de JDK de Sun admite las versiones de formato de archivo de clase 45.0 a 45.3 inclusive. Las versiones 1.1.X de JDK de Sun pueden admitir formatos de archivo 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 archivo 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.


¿Puede dar un enlace más detallado por favor?
mstrap

Creo que por "plataforma" se refieren a la plataforma Java, no al sistema operativo. Por supuesto, al indicar a javac 1.7 que cree archivos de clase compatibles con 1.6, habrá una diferencia.
mstrap

@mtk +1 para mostrar cuántas propiedades se generan para una sola clase durante la compilación.
gaborsch

3

En primer lugar, no existe absolutamente tal garantía en la especificación. Un compilador conforme podría marcar la hora 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 a nivel de bytes en cada compilación, y de manera trivial.

En segundo lugar, incluso sin trucos tan 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 hace describir el nombre del archivo fuente como uno de los atributos estándar, y la adición de líneas en blanco al archivo de origen bien 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 (aparte de la que se atribuye a las diferencias en lo que había en la ruta de clases). El código que variaría según la plataforma (es decir, bibliotecas de código nativo) no es parte del archivo de clase, y la generación real de código nativo a partir del código de bytes ocurre después de que se carga la clase.

En cuarto lugar (y lo más importante), apesta a un mal olor a proceso (como un olor a código, pero por cómo actúa sobre el código) querer saber esto. Versión de la fuente si es posible, no de la compilación, y si necesita versionar la compilación, la versión a nivel de componente completo y no en archivos de clases individuales. De preferencia, use un servidor CI (como Jenkins) para administrar el proceso de convertir la fuente en código ejecutable.


2

Creo que, si usa el mismo JDK, el código de bytes generado será siempre el mismo, sin relación con el hardware y el sistema operativo utilizado. La producción del código de bytes la realiza el compilador de Java, que utiliza un algoritmo determinista para "transformar" el código fuente en código de bytes. Entonces, la salida siempre será la misma. En estas condiciones, solo una actualización del código fuente afectará la salida.


3
¿Tiene una referencia para esto? Como dije en los comentarios de la pregunta, este definitivamente no es el caso de C # , por lo que me encantaría ver una referencia que indique que es el caso de Java. En particular, estoy pensando que un compilador de subprocesos múltiples podría asignar diferentes nombres de identificador en diferentes ejecuciones.
RB.

1
Esta es la respuesta a mi pregunta y lo que esperaría, sin embargo, estoy de acuerdo con RB en que una referencia para eso sería importante.
mstrap

Yo creo lo mismo. No creo que encuentres una referencia definitiva. Si es importante para ti, puedes hacer un estudio. Reúna algunos de los principales y pruébelos en diferentes plataformas compilando código fuente abierto. Compare los archivos de bytes. Publica el resultado. Asegúrese de poner un enlace aquí.
emory

1

En general, tengo que decir que no hay garantía de que la misma fuente produzca el mismo código de bytes cuando la compila el mismo compilador pero en una plataforma diferente.

Buscaría escenarios que involucren diferentes idiomas (páginas de códigos), por ejemplo, Windows con soporte para el idioma japonés. Piense en caracteres de varios bytes; a menos que el compilador siempre asuma que necesita admitir todos los lenguajes que 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 versión a versión en SOM (Forman, Conner, Danforth y Raper, Proceedings of OOPSLA '95), los binarios del lenguaje de programación Java son compatibles con todas las transformaciones relevantes que los autores identifican (con algunas salvedades 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 métodos, constructores e inicializadores existentes para mejorar el rendimiento.

• Cambiar métodos o constructores para devolver valores en entradas para las que previamente arrojaron excepciones que normalmente no deberían ocurrir o fallaron al entrar en un bucle infinito o causar 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, se eliminan los campos de acceso, métodos o constructores de clases e interfaces predeterminados (solo paquetes) en el paquete.

• Reordenar 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.

• Reordenar la lista de superinterfaces directas de una clase o interfaz.

• Insertar nuevos tipos de clase o interfaz en la jerarquía de tipos.

Este capítulo especifica los estándares mínimos para la compatibilidad binaria garantizada 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 provienen 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 las versiones de la plataforma Java SE está fuera del alcance de este capítulo.


Ese artículo analiza lo que puede suceder si cambiamos la versión de Java. La pregunta del OP era qué puede pasar si cambiamos de plataforma dentro de la misma versión de Java. De lo contrario, es una buena captura.
gaborsch

1
Es lo más cercano que pude encontrar. Hay un hueco extraño entre la especificación del idioma y la especificación de la JVM. Hasta ahora, tendría que responder al OP con 'no hay garantía de que el mismo compilador java produzca el mismo código de bytes cuando se ejecute en una plataforma diferente'.
Kelly S. French

1

Java allows you write/compile code on one platform and run on different platform. AFAIK ; esto será posible solo cuando el archivo de clase generado en una plataforma diferente sea igual o técnicamente idéntico, es decir, idéntico.

Editar

Lo que quiero decir con técnicamente el mismo comentario es eso. No es necesario que sean 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 por byte.


La pregunta del OP era si los archivos de clases eran iguales o "técnicamente iguales".
bdesham

Me interesa si son idénticos .
mstrap

y la respuesta es sí. lo que quiero decir es que pueden no ser iguales si comparas byte por byte, por eso usé la palabra técnicamente igual.
rai.skumar

@bdesham quería saber si son idénticos. no estoy seguro de lo que entendiste por "técnicamente igual" ... ¿es ese el motivo del voto negativo?
rai.skumar

@ rai.skumar Su respuesta básicamente dice: "Dos compiladores siempre producirán una salida que se comporte de la misma manera". Por supuesto esto es verdad; es toda la motivación de la plataforma Java. El OP quería saber si el código emitido era idéntico byte por byte , lo que no abordó en su respuesta.
bdesham

1

Para la pregunta:

"¿Cuáles son las circunstancias en las que el mismo ejecutable javac, cuando se ejecuta en una plataforma diferente, producirá un código de bytes diferente?"

El ejemplo de compilación cruzada muestra cómo podemos usar la opción Javac: -target version

Esta bandera 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 clases diferirán según los atributos que proporcionemos durante la comparación con esta opción.


0

Lo más probable es que la respuesta sea "sí", pero para tener una respuesta precisa, es necesario buscar algunas claves o generación de guid durante la compilación.

No recuerdo la situación en la que ocurre esto. Por ejemplo, para tener un ID con fines de serialización, está codificado, es decir, generado por el programador o el IDE.

PD: También JNI puede importar.

PPS encontré que javacestá escrito en java. Esto significa que es idéntico en diferentes plataformas. Por lo tanto, no generaría un código diferente sin una razón. Por lo tanto, solo puede hacer esto con llamadas nativas.


Tenga en cuenta que Java no lo protege de todas las diferencias de plataforma. El orden de los archivos devueltos cuando se enumera el contenido del directorio no está definido, y esto posiblemente podría tener algún impacto en un compilador.
Joachim Sauer

0

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 haberla. 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 que existe actualmente produce el mismo código de bytes en todas las circunstancias (hardware diferente, etc.), la respuesta 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 pasa, por ejemplo, de 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 administración de la configuración es su razón para hacer la pregunta, pero es una razón comprensible para preocuparse. Comparar 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 lo hicieron los archivos fuente.


0

Lo diría de otra manera.

Primero, creo que la pregunta no se trata de ser determinista:

Por supuesto que es determinista: la aleatoriedad es difícil de lograr en informática, y no hay razón para que un compilador la introduzca aquí por ningún motivo.

En segundo lugar, si lo reformula con "¿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 asegurarse de esto es dejando el .class (o .pyc en mi caso) en su etapa de git. Se dará cuenta de que entre las diferentes computadoras de su equipo, git nota cambios entre los archivos .pyc, cuando no se introdujeron cambios en el archivo .py (y .pyc se volvió a compilar de todos modos).

Al menos eso es lo que observé. ¡Así que ponga * .pyc y * .class en su .gitignore!

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.