Java类文件的创建是否确定?

时间:2013-02-20 16:30:48

标签: java compiler-construction javac

使用相同的JDK (即相同的javac可执行文件)时,生成的类文件是否始终相同?根据操作系统硬件,会有区别吗?除JDK版本外,是否还有其他因素导致差异?是否有任何编译器选项可以避免差异?仅在理论上有所不同,或者Oracle的javac是否实际为相同的输入和编译器选项生成不同的类文件?

更新1 我对生成感兴趣,即编译器输出,而不是类文件是否可以在各种平台上运行

更新2 “相同的JDK”,我的意思是javac可执行文件。

更新3 Oracle编译器的理论差异与实际差异之间的区别。

[编辑,添加转述问题]
“在不同的平台上运行相同的javac可执行文件会产生不同的字节码的情况是什么?”

11 个答案:

答案 0 :(得分:68)

我们这样说吧:

在给定相同的.class文件的情况下,我可以轻松生成一个完全符合标准的Java编译器,该编译器永远不会生成两次相同的.java文件。

我可以通过调整各种字节码构造或简单地向我的方法添加多余的属性(允许)来实现这一点。

鉴于规范要求编译器生成逐字节的相同类文件,我避免依赖这样的结果。

然而,我已经检查了几次,使用相同的编译器使用相同的开关(和相同的库!)编译相同的源文件 结果在相同的.class个文件中。

更新:我最近偶然发现this interesting blog post about the implementation of switch on String in Java 7。在这篇博文中,我将引用一些相关部分(强调我的):

  

为了使编译器的输出可预测且可重复,这些数据结构中使用的映射和集合为LinkedHashMapLinkedHashSet而不仅仅是HashMaps和{{1} }。 在给定编译期间生成代码的功能正确性方面使用HashSetsHashMap就可以了;迭代顺序无关紧要。但是,我们发现HashSet的输出不会因系统类的实现细节而有所不同

这很清楚地说明了问题:编译器不需要以确定的方式操作,只要它与规范匹配即可。然而,编译器开发人员意识到尝试 通常一个好主意(假设它不太昂贵,可能)。

答案 1 :(得分:38)

编译器没有义务在每个平台上生成相同的字节码。您应该咨询不同供应商的javac实用程序以获得特定答案。


我将通过文件排序显示一个实际的例子。

假设我们有2个jar文件:my1.jarMy2.jar。它们并排放在lib目录中。编译器按字母顺序读取它们(因为这是lib),但是当文件系统不区分大小写时,顺序为my1.jarMy2.jarMy2.jar,{{ 1}}如果它区分大小写。

my1.jar有一个类my1.jar,方法

A.class

public class A { public static void a(String s) {} } 具有相同的My2.jar,但使用不同的方法签名(接受A.class):

Object

很明显,如果你有一个电话

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

它将在不同情况下编译具有不同签名的方法调用。因此,取决于您的文件系统区分大小写,结果会得到不同的类。

答案 2 :(得分:6)

简答 -


长答案

他们bytecode对于不同的平台不一定相同。它是JRE(Java运行时环境),它知道如何执行字节码。

如果你浏览Java VM specification,你就会知道不一定要对不同平台的字节码是相同的。

通过the class file format,它将类文件的结构显示为

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

检查次要版本和主要版本

  

minor_version,major_version

     

minor_version和。的值   major_version项是此次要版本号和主要版本号   class file.Together,主要版本号和次要版本号确定   类文件格式的版本。如果类文件具有主要版本   数字M和次要版本号m,我们表示它的版本   类文件格式为M.m.因此,类文件格式版本可以是   按字典顺序排序,例如,1.5< 2.0< 2.1。一个Java   虚拟机实现可以支持类文件格式   版本v当且仅当v位于某个连续范围Mi.0 v   Mj.m.只有Sun可以指定Java虚拟的版本范围   机器实现符合某个发布级别   Java平台可能会支持。

通过脚注阅读更多内容

  

1 Sun的JDK版本1.0.2的Java虚拟机实现   支持类文件格式版本45.0到45.3(含)。 Sun公司   JDK发行版1.1.X可以支持版本中的类文件格式   范围45.0到45.65535(含)。版本1.2的实现   Java 2平台可以支持版本的类文件格式   范围45.0到46.0,包括在内。

因此,调查所有这些表明在不同平台上生成的类文件不必相同。

答案 3 :(得分:3)

首先,规范中绝对没有这样的保证。符合标准的编译器可以将编译时间标记为生成的类文件作为附加(自定义)属性,并且类文件仍然是正确的。然而,它会在每个构建上产生一个字节级别的不同文件,而且非常简单。

其次,即使没有这些讨厌的技巧,也没有理由期望编译器连续两次做同样的事情,除非它的配置和它的输入在两种情况下是相同的。规范 将源文件名描述为标准属性之一,并且向源文件添加空行可能会更改行号表。

第三,由于主机平台,我从未遇到任何构建上的差异(除了可归因于类路径上的差异)。基于平台(即本机代码库)而变化的代码不是类文件的一部分,并且在加载类之后,字节码中实际生成本机代码。

第四(也是最重要的)它充满了糟糕的过程气味(就像代码味道,但对于你如何对代码采取行动)想知道这一点。如果可能,请为源代码版本,而不是版本,如果您确实需要在整个组件级别而不是在单个类文件上对版本,版本进行版本控制。首选,使用CI服务器(如Jenkins)来管理将源代码转换为可运行代码的过程。

答案 4 :(得分:2)

我相信,如果使用相同的JDK,生成的字节代码将始终相同,与使用的硬件和操作系统无关。字节码生成由java编译器完成,它使用确定性算法将源代码“转换”为字节代码。因此,输出将始终相同。在这些情况下,只有源代码更新才会影响输出。

答案 5 :(得分:1)

Java allows you write/compile code on one platform and run on different platform. AFAIK ;只有当在不同平台上生成的类文件相同或技术相同,即相同时,才可能实现这一点。

修改

我的意思是技术上相同的评论是这样的。如果逐字节比较,它们不需要完全相同。

因此,根据规范,不同平台上的类的.class文件不需要逐字节匹配。

答案 6 :(得分:1)

总的来说,我不得不说,在相同的编译器编译但是在不同的平台上时,不能保证相同的源会产生相同的字节码。

我会研究涉及不同语言(代码页)的场景,例如支持日语的Windows。想想多字节字符;除非编译器总是假设它需要支持它可能针对8位ASCII优化的所有语言。

Java Language Specification中有一个关于二进制兼容性的部分。

  

在SOM中的Release-to-Release二进制兼容性框架内   (Forman,Conner,Danforth和Raper,OOPSLA '95的会议录),Java   编程语言二进制文件在所有相关的二进制兼容   作者确定的转变(有一些警告   关于添加实例变量)。使用他们的计划,   这里列出了一些重要的二进制兼容变化   Java编程语言支持:

     

•重新实现现有的方法,构造函数和初始化器   提高绩效。

     

•更改方法或构造函数以返回输入的值   他们以前要么抛出通常不应该发生的异常   或者因进入无限循环或导致死锁而失败。

     

•将新字段,方法或构造函数添加到现有类或   接口。

     

•删除类的私有字段,方法或构造函数。

     

•更新整个软件包时,删除默认值(仅限软件包)   访问类和接口的字段,方法或构造函数   包裹。

     

•重新排序现有类型中的字段,方法或构造函数   宣言。

     

•在类层次结构中向上移动方法。

     

•重新排序类或类的直接超接口列表   接口。

     

•在类型层次结构中插入新的类或接口类型。

     

本章规定了二进制兼容性的最低标准   由所有实施保证。 Java编程语言   保证类和接口的二进制文件时的兼容性   混合的,不知道来自兼容的来源,但其中   源已经以此处描述的兼容方式进行了修改。注意   我们正在讨论版本之间的兼容性   应用。讨论Java版本之间的兼容性   SE平台超出了本章的范围。

答案 7 :(得分:1)

问题:

  

“在不同的平台上运行相同的javac可执行文件会产生不同的字节码的情况是什么?”

Cross-Compilation example显示了我们如何使用Javac选项:-target version

此标志生成的类文件与我们在调用此命令时指定的Java版本兼容。因此,类文件将根据我们在使用此选项进行的比较期间提供的属性而有所不同。

答案 8 :(得分:0)

最有可能的答案是“是”,但要得到准确答案,需要在编译期间搜索一些键或guid生成。

我不记得发生这种情况的情况。例如,为了具有用于序列化目的的ID,它是硬编码的,即由程序员或IDE生成。

P.S。 JNI也很重要。

P.P.S。我发现javac本身就是用java编写的。这意味着它在不同平台上是相同的。因此,如果没有理由,它就不会产生不同的代码。因此,它只能通过本机调用来实现。

答案 9 :(得分:0)

我会用另一种方式。

首先,我认为问题不在于确定性:

当然它是确定性的:随机性很难在计算机科学中实现,并且没有理由编译器会出于任何原因在这里引入它。

其次,如果你通过“相同源代码文件的字节码文件有多相似?”来重新表述它,那么 ,你不能依赖它他们将是相似的事实

确保这一点的好方法是将.class(或我的情况下的.pyc)留在你的git阶段。您将意识到,在您的团队中的不同计算机中,当没有对.py文件进行任何更改(以及.pyc重新编译)时,git会注意到.pyc文件之间的更改。

至少那是我观察到的。所以把* .pyc和* .class放在.gitignore中!

答案 10 :(得分:0)

有两个问题。

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

这是一个理论问题,答案很清楚,是的,可以。正如其他人所说,规范不要求编译器生成逐字节的相同类文件。

即使当前存在的每个编译器在所有情况下(不同的硬件等)都产生相同的字节代码,明天的答案也许会有所不同。如果您从未计划更新javac或您的操作系统,您可以在特定情况下测试该版本的行为,但是如果您从例如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?

这是不可知的。

我不知道配置管理是否是您提出问题的理由,但这是一个可以理解的理由。比较字节代码是合法的IT控件,但仅用于确定类文件是否更改,而不是确定源文件是否更改。