A criação de arquivos de classe Java é determinística?

94

Ao usar o mesmo JDK (ou seja, o mesmo javacexecutável), os arquivos de classe gerados são sempre idênticos? Pode haver uma diferença dependendo do sistema operacional ou hardware ? Exceto na versão JDK, poderia haver algum outro fator resultando em diferenças? Existem opções do compilador para evitar diferenças? Existe uma diferença apenas em teoria ou o Oracle javacrealmente produz arquivos de classe diferentes para a mesma entrada e opções de compilador?

Atualização 1 Estou interessado na geração , ou seja, saída do compilador, não se um arquivo de classe pode ser executado em várias plataformas.

Atualização 2 Por 'Mesmo JDK', também quero dizer o mesmo javacexecutável.

Atualização 3 Distinção entre diferença teórica e diferença prática em compiladores Oracle.

[EDITAR, adicionando a questão parafraseada]
"Quais são as circunstâncias em que o mesmo executável javac, quando executado em uma plataforma diferente, produzirá bytecode diferente?"

mstrap
fonte
5
@Gamb CORA não significa que o código de bytes será exatamente o mesmo se compilado em plataformas diferentes; tudo o que significa é que o código de byte gerado fará exatamente a mesma coisa.
dasblinkenlight
10
Por quê você se importa? Isso cheira a um problema XY .
Joachim Sauer
4
@JoachimSauer Considere se você controla a versão de seus binários - você pode querer detectar mudanças apenas se o código-fonte tiver mudado, mas você saberia que não seria uma ideia sensata se o JDK pudesse alterar arbitrariamente os binários de saída.
RB.
7
@RB .: o compilador tem permissão para produzir qualquer código de byte em conformidade que represente o código compilado. Na verdade, algumas atualizações do compilador corrigem bugs que produzem código ligeiramente diferente (geralmente com o mesmo comportamento de tempo de execução). Em outras palavras: se você deseja detectar mudanças na fonte, verifique as mudanças na fonte.
Joachim Sauer
3
@dasblinkenlight: você está assumindo que a resposta que eles afirmam ter é realmente correta e atualizada (duvidoso, visto que a pergunta é de 2003).
Joachim Sauer

Respostas:

68

Vamos colocar desta forma:

Posso produzir facilmente um compilador Java totalmente compatível que nunca produz o mesmo .classarquivo duas vezes, dado o mesmo .javaarquivo.

Eu poderia fazer isso ajustando todos os tipos de construção de bytecode ou simplesmente adicionando atributos supérfluos ao meu método (o que é permitido).

Dado que a especificação não exige que o compilador produza arquivos de classe idênticos byte a byte, eu evitaria depender de tal resultado.

No entanto , as poucas vezes que eu verificados, compilando o mesmo arquivo de origem com o mesmo compilador com as mesmas opções (e as mesmas bibliotecas!) Fez resultar no mesmo .classarquivos.

Atualização: recentemente tropecei neste interessante post de blog sobre a implementação do switchem Stringno Java 7 . Nesta postagem do blog, existem algumas partes relevantes, que citarei aqui (grifo meu):

Para tornar a saída do compilador previsível e repetível, os mapas e conjuntos usados ​​nessas estruturas de dados são LinkedHashMaps e LinkedHashSets em vez de apenas HashMapse HashSets. Em termos de correção funcional do código gerado durante uma determinada compilação, usar HashMape HashSetseria ótimo ; a ordem de iteração não importa. No entanto, achamos benéfico que a javacsaída de não varie com base nos detalhes de implementação das classes do sistema .

Isso ilustra muito claramente o problema: o compilador não é obrigado a agir de maneira determinística, desde que corresponda às especificações. Os desenvolvedores do compilador, entretanto, percebem que geralmente é uma boa idéia tentar (desde que não seja muito caro, provavelmente).

Joachim Sauer
fonte
@GaborSch o que está faltando? "Quais são as circunstâncias em que o mesmo executável javac, quando executado em uma plataforma diferente, produzirá bytecode diferente?" basicamente dependendo do capricho do grupo que produziu o compilador
emório de
3
Bem, para mim, isso seria motivo suficiente para não depender dele: um JDK atualizado poderia quebrar meu sistema de construção / arquivamento se eu dependesse do fato de que o compilador sempre produz o mesmo código.
Joachim Sauer
3
@GaborSch: você já tem um exemplo perfeitamente bom de tal situação, então alguma visão adicional sobre o problema era necessária. Não faz sentido duplicar seu trabalho.
Joachim Sauer
1
@GaborSch A raiz do problema é que queremos implementar uma "atualização online" eficiente de nosso aplicativo, para o qual os usuários buscariam apenas JARs modificados no site. Posso criar JARs idênticos com arquivos de classe idênticos como entrada. Mas a questão é se os arquivos de classe são sempre idênticos quando compilados a partir dos mesmos arquivos de origem. Todo o nosso conceito permanece e falha com este fato.
mstrap de
2
@mstrap: então é um problema XY, afinal. Bem, você pode olhar para atualizações diferenciais de jars (então mesmo diferenças de um byte não faria com que todo o jar fosse baixado novamente) e você deve fornecer números de versão explícitos para seus lançamentos de qualquer maneira, de modo que todo esse ponto é discutível, na minha opinião .
Joachim Sauer
38

Não há nenhuma obrigação para os compiladores de produzir o mesmo bytecode em cada plataforma. Você deve consultar o javacutilitário dos diferentes fornecedores para obter uma resposta específica.


Vou mostrar um exemplo prático disso com a ordenação de arquivos.

Digamos que temos 2 arquivos jar: my1.jare My2.jar. Eles são colocados no libdiretório, lado a lado. O compilador lê-os em ordem alfabética (desde que seja lib), mas a ordem é my1.jar, My2.jarquando o sistema de arquivos não diferencia maiúsculas de minúsculas e My2.jar, my1.jarse for sensível a maiúsculas e minúsculas.

O my1.jartem uma classe A.classcom um método

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

O My2.jartem o mesmo A.class, mas com assinatura de método diferente (aceita Object):

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

É claro que se você tiver uma chamada

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

ele irá compilar uma chamada de método com assinatura diferente em casos diferentes. Portanto, dependendo da sensibilidade de caixa do seu sistema de arquivos, você obterá classes diferentes como resultado.

gaborsch
fonte
1
+1 Existem inúmeras diferenças entre o compilador Eclipse e javac, por exemplo, como os construtores sintéticos são gerados .
Paul Bellora de
2
@GaborSch Estou interessado em saber se o código de byte é idêntico para o mesmo JDK, ou seja, o mesmo javac. Vou deixar isso mais claro.
mstrap de
2
@mstrap Entendi sua pergunta, mas a resposta ainda é a mesma: depende do fornecedor. O javacnão é o mesmo, porque você tem binários diferentes em cada plataforma (por exemplo, Win7, Linux, Solaris, Mac). Para um fornecedor, não faz sentido ter implementações diferentes, mas qualquer problema específico da plataforma pode influenciar o resultado (por exemplo, pedido de flie em um diretório (pense em seu libdiretório), endianness, etc).
gaborsch de
1
Normalmente, a maioria javacé implementada em Java (e javacé apenas um iniciador nativo simples), portanto, a maioria das diferenças de plataforma não deve ter impacto.
Joachim Sauer de
2
@mstrap - o que ele quer dizer é que não há nenhum requisito para nenhum fornecedor fazer seu compilador produzir exatamente o mesmo bytecode em todas as plataformas, apenas que o bytecode resultante produza os mesmos resultados. Dado que não existe um padrão / especificação / requisito, a resposta à sua pergunta é "Depende do fornecedor, compilador e plataforma específicos".
Brian Roach de
6

Resposta curta - NÃO


Resposta longa

Eles bytecodenão precisam ser iguais para plataformas diferentes. É o JRE (Java Runtime Environment) que sabe exatamente como executar o bytecode.

Se você passar pela especificação Java VM, saberá que não precisa ser verdade que o bytecode é o mesmo para plataformas diferentes.

Percorrendo o formato do arquivo de classe , mostra a estrutura de um arquivo de classe 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];
}

Verificando sobre a versão secundária e principal

minor_version, major_version

Os valores dos itens minor_version e major_version são os números de versão secundária e principal deste arquivo de classe. Juntos, um número de versão principal e um número de versão secundária determinam a versão do formato do arquivo de classe. Se um arquivo de classe tem o número de versão principal M e o número de versão secundária m, denotamos a versão de seu formato de arquivo de classe como Mm. Assim, as versões de formato de arquivo de classe podem ser ordenadas lexicograficamente, por exemplo, 1,5 <2,0 <2,1. Uma implementação de máquina virtual Java pode suportar um formato de arquivo de classe da versão v se e somente se v estiver em algum intervalo contíguo Mi.0 v Mj.m. Somente a Sun pode especificar a faixa de versões que uma implementação de máquina virtual Java compatível com um determinado nível de lançamento da plataforma Java pode suportar.1

Ler mais nas notas de rodapé

1 A implementação da máquina virtual Java do JDK versão 1.0.2 da Sun oferece suporte às versões de formato de arquivo de classe 45.0 a 45.3 inclusive. O JDK da Sun, lançamentos 1.1.X, pode suportar formatos de arquivo de classe de versões na faixa de 45.0 a 45.65535 inclusive. As implementações da versão 1.2 da plataforma Java 2 podem oferecer suporte a formatos de arquivo de classe de versões na faixa de 45,0 a 46,0 inclusive.

Portanto, investigar tudo isso mostra que os arquivos de classe gerados em diferentes plataformas não precisam ser idênticos.

mtk
fonte
Você pode dar um link mais detalhado, por favor?
mstrap de
Acho que por 'plataforma' eles se referem à plataforma Java, não ao sistema operacional. Obviamente, ao instruir o javac 1.7 a criar arquivos de classe compatíveis com o 1.6, haverá uma diferença.
mstrap de
@mtk +1 para mostrar quantas propriedades são geradas para uma única classe durante a compilação.
gaborsch de
3

Em primeiro lugar, não há absolutamente nenhuma garantia nas especificações. Um compilador em conformidade poderia carimbar a hora da compilação no arquivo de classe gerado como um atributo adicional (personalizado) e o arquivo de classe ainda estaria correto. No entanto, ele produziria um arquivo diferente de nível de byte em cada construção, e de maneira trivial.

Em segundo lugar, mesmo sem esses truques desagradáveis, não há razão para esperar que um compilador faça exatamente a mesma coisa duas vezes seguidas, a menos que sua configuração e sua entrada sejam idênticas nos dois casos. A especificação faz descrever o nome do arquivo fonte como um dos atributos padrão, e adicionar linhas em branco para o arquivo de origem poderia muito bem mudar a tabela de número de linha.

Em terceiro lugar, nunca encontrei qualquer diferença na construção devido à plataforma host (além daquela que era atribuível às diferenças no que estava no caminho de classe). O código que variaria com base na plataforma (ou seja, bibliotecas de código nativo) não faz parte do arquivo de classe, e a geração real do código nativo do bytecode acontece depois que a classe é carregada.

Em quarto lugar (e mais importante), cheira a um mau cheiro de processo (como um cheiro de código, mas para como você age no código) querer saber disso. Versão da fonte, se possível, não da construção, e se você precisar fazer a versão da construção, versão no nível do componente inteiro e não em arquivos de classe individuais. De preferência, use um servidor CI (como Jenkins) para gerenciar o processo de transformar a origem em código executável.

Donal Fellows
fonte
2

Acredito que, se você usar o mesmo JDK, o byte code gerado será sempre o mesmo, sem relação com o harware e SO utilizado. A produção do código de byte é feita pelo compilador java, que usa um algoritmo determinístico para "transformar" o código fonte em código de byte. Portanto, a saída será sempre a mesma. Nessas condições, apenas uma atualização do código-fonte afetará a saída.

viniciusjssouza
fonte
3
Você tem uma referência para isso? Como eu disse nos comentários da pergunta, esse definitivamente não é o caso do C # , então adoraria ver uma referência afirmando que é o caso do Java. Estou particularmente pensando que um compilador multi-threaded pode atribuir nomes de identificador diferentes em execuções diferentes.
RB.
1
Essa é a resposta a minha dúvida e o que eu esperava, porém concordo com a RB que uma referência para isso seria importante.
mstrap de
Eu acredito o mesmo. Não acho que você encontrará uma referência definitiva. Se for importante para você, você pode fazer um estudo. Reúna vários dos principais e experimente-os em diferentes plataformas, compilando algum código-fonte aberto. Compare os arquivos de bytes. Publique o resultado. Certifique-se de colocar um link aqui.
emory
1

No geral, devo dizer que não há garantia de que a mesma fonte produzirá o mesmo bytecode quando compilada pelo mesmo compilador, mas em uma plataforma diferente.

Eu examinaria cenários envolvendo diferentes idiomas (páginas de código), por exemplo, Windows com suporte ao idioma japonês. Pense em personagens multibyte; a menos que o compilador sempre presuma que ele precisa oferecer suporte a todas as linguagens, ele pode otimizar para ASCII de 8 bits.

Há uma seção sobre compatibilidade binária na Especificação da linguagem Java .

No âmbito da Compatibilidade Binária Release-to-Release em SOM (Forman, Conner, Danforth e Raper, Proceedings of OOPSLA '95), os binários da linguagem de programação Java são binários compatíveis em todas as transformações relevantes que os autores identificam (com algumas ressalvas com respeito à adição de variáveis ​​de instância). Usando seu esquema, aqui está uma lista de algumas mudanças binárias compatíveis importantes que a linguagem de programação Java suporta:

• Reimplementar métodos, construtores e inicializadores existentes para melhorar o desempenho.

• Alterar métodos ou construtores para retornar valores em entradas para as quais eles lançaram exceções que normalmente não deveriam ocorrer ou falharam entrando em um loop infinito ou causando um deadlock.

• Adicionar novos campos, métodos ou construtores a uma classe ou interface existente.

• Excluindo campos privados, métodos ou construtores de uma classe.

• Quando um pacote inteiro é atualizado, excluindo campos de acesso padrão (apenas pacote), métodos ou construtores de classes e interfaces no pacote.

• Reordenar os campos, métodos ou construtores em uma declaração de tipo existente.

• Mover um método para cima na hierarquia de classes.

• Reordenar a lista de superinterfaces diretas de uma classe ou interface.

• Inserir novas classes ou tipos de interface na hierarquia de tipos.

Este capítulo especifica os padrões mínimos para compatibilidade binária garantida por todas as implementações. A linguagem de programação Java garante compatibilidade quando binários de classes e interfaces são misturados que não são conhecidos por serem de fontes compatíveis, mas cujas fontes foram modificadas nas formas compatíveis descritas aqui. Observe que estamos discutindo a compatibilidade entre as versões de um aplicativo. Uma discussão sobre compatibilidade entre as versões da plataforma Java SE está além do escopo deste capítulo.

Kelly S. French
fonte
Esse artigo discute o que pode acontecer quando mudamos a versão Java. A pergunta do OP era o que pode acontecer se mudarmos de plataforma dentro da mesma versão do Java. Caso contrário, é uma boa captura.
gaborsch de
1
É o mais próximo que consegui encontrar. Há um buraco estranho entre as especificações do idioma e as especificações da JVM. Até agora, eu teria que responder ao OP com 'não há garantia de que o mesmo compilador java produzirá o mesmo bytecode quando executado em uma plataforma diferente'.
Kelly S. French de
1

Java allows you write/compile code on one platform and run on different platform. AFAIK ; isso só será possível quando o arquivo de classe gerado em plataforma diferente for o mesmo ou tecnicamente igual, ou seja, idêntico.

Editar

O que quero dizer com tecnicamente mesmo comentário é isso. Eles não precisam ser exatamente iguais se você comparar byte por byte.

Portanto, de acordo com a especificação do arquivo .class de uma classe em diferentes plataformas, não é necessário corresponder byte a byte.

rai.skumar
fonte
A pergunta do OP era se os arquivos de classe eram os mesmos ou "tecnicamente os mesmos".
bdesham de
Estou interessado em saber se eles são idênticos .
mstrap de
e a resposta é sim. o que quero dizer é que eles podem não ser os mesmos se você comparar byte por byte, é por isso que usei a palavra tecnicamente igual.
rai.skumar de
@bdesham ele queria saber se eles são idênticos. não tenho certeza do que você entendeu por "tecnicamente o mesmo" ... esse é o motivo do downvote?
rai.skumar de
@ rai.skumar Sua resposta basicamente diz: "Dois compiladores sempre produzirão uma saída que se comporta da mesma forma." Claro que isso é verdade; é toda a motivação da plataforma Java. O OP queria saber se o código emitido era byte para byte idêntico , o que você não abordou em sua resposta.
bdesham de
1

Para a pergunta:

"Quais são as circunstâncias em que o mesmo executável javac, quando executado em uma plataforma diferente, produzirá bytecode diferente?"

O exemplo de compilação cruzada mostra como podemos usar a opção Javac: -target version

Este sinalizador gera arquivos de classe que são compatíveis com a versão Java que especificamos ao invocar este comando. Portanto, os arquivos de classe serão diferentes dependendo dos atributos que fornecemos durante a compensação usando esta opção.

PhilipJoseParampettu
fonte
0

Muito provavelmente, a resposta é "sim", mas para ter uma resposta precisa, é necessário pesquisar algumas chaves ou geração de guid durante a compilação.

Não consigo me lembrar da situação em que isso ocorre. Por exemplo, para ter ID para fins de serialização, ele é codificado, ou seja, gerado pelo programador ou IDE.

PS Também JNI pode importar.

PPS eu descobri que javacé escrito em java. Isso significa que é idêntico em plataformas diferentes. Portanto, ele não geraria um código diferente sem um motivo. Portanto, ele pode fazer isso apenas com chamadas nativas.

Suzan Cioc
fonte
Observe que o Java não protege você de todas as diferenças de plataforma. A ordem dos arquivos retornados ao listar o conteúdo do diretório não é definida, e isso pode ter algum impacto sobre o compilador.
Joachim Sauer de
0

Existem duas questões.

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

Esta é uma questão teórica e a resposta é clara, sim, pode haver. Como já foi dito, a especificação não exige que o compilador produza arquivos de classe idêntica byte a byte.

Mesmo se cada compilador existente produzisse o mesmo código de bytes em todas as circunstâncias (hardware diferente, etc.), a resposta amanhã pode ser diferente. Se você nunca planeja atualizar o javac ou seu sistema operacional, pode testar o comportamento dessa versão em suas circunstâncias particulares, mas os resultados podem ser diferentes se você for, por exemplo, Java 7 Update 11 para Java 7 Update 15.

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

Isso é incognoscível.

Não sei se o gerenciamento de configuração é o motivo para fazer a pergunta, mas é um motivo compreensível para se preocupar. Comparar os códigos de byte é um controle legítimo de TI, mas apenas para determinar se os arquivos de classe mudaram, não para determinar se os arquivos de origem mudaram.

Pular Addison
fonte
0

Eu colocaria de outra forma.

Em primeiro lugar, acho que a questão não é ser determinista:

É claro que é determinístico: a aleatoriedade é difícil de ser alcançada na ciência da computação e não há razão para um compilador introduzi-la aqui por qualquer motivo.

Em segundo lugar, se você reformulá-lo "quão semelhantes são os arquivos de bytecode para um mesmo arquivo de código-fonte?", Então Não , você não pode confiar no fato de que eles serão semelhantes .

Uma boa maneira de ter certeza disso é deixando o .class (ou .pyc no meu caso) em seu estágio git. Você perceberá que, entre os diferentes computadores de sua equipe, o git percebe alterações entre os arquivos .pyc, quando nenhuma alteração foi trazida para o arquivo .py (e .pyc recompilado de qualquer maneira).

Pelo menos foi o que observei. Portanto, coloque * .pyc e * .class no seu .gitignore!

Augustin Riedinger
fonte