【问题标题】:Identical Java sources compile to binary differing classes相同的 Java 源代码编译为二进制不同的类
【发布时间】:2012-10-01 11:16:44
【问题描述】:

谁能解释相同的 Java 源代码如何最终编译为 二进制 不同的类文件?

问题源于以下情况:

我们有一个相当大的应用程序(800 多个类),它已经被分支、重组然后重新集成回主干。在重新整合之前,我们将主干合并到分支中,这是标准程序。

最终结果是一组带有分支源的目录和一组带有主干源的目录。使用 Beyond Compare,我们能够确定两组来源是相同的。然而,在编译(相同的 JDK 使用 IntelliJ v11 中托管的 maven)时,我们注意到大约有十几个类文件是不同的。

当我们反编译每对明显不同的类文件的源代码时,我们最终得到了相同的 java 源代码,所以就最终结果而言,这似乎并不重要。但是为什么只有几个文件不同呢?

谢谢。


补充一点:

如果 maven/javac 以不同的顺序编译文件,会不会影响最终结果?

【问题讨论】:

  • 不同的jdk版本?我想不同版本的优化可能会有所不同。
  • 使用 javap -c -v(感谢 Peter Lawrey)并使用 Beyond Compare(很棒的工具,喜欢它!)查看各自的输出,我可以确认 Stephen C(感谢 Stephen C)响应中的第 5 项在这里给出部分答案。在某些情况下,常量池的顺序是不同的。但是,我很确定两者的类路径相同,但编译顺序可能不同。

标签: java compilation


【解决方案1】:

假设JDK版本、构建工具版本和构建/编译选项是相同的,我仍然可以想到许多可能的差异来源:

  1. 时间戳 - 类文件可能1 包含编译时间戳。除非您在完全相同的时间运行编译,否则同一文件的不同编译将导致不同的时间戳。

  2. 源文件名路径 - 每个类文件都包含源文件的路径名。如果你用不同的路径名编译两棵树,类文件将包含不同的源路径名。

  3. 1234563的常量被合并到As 类文件中。因此,如果您针对不同版本的B(常量值不同)编译AA 的代码可能会有所不同。
  4. 编译器在HashMap 键中使用identityHashcode 导致的差异可能会导致某些步骤中映射迭代顺序的差异。这可能会以不显着的方式影响.class 文件的生成,但仍显示为.class 文件差异。例如,常量池条目可能以不同的顺序结束。

  5. 外部类/方法的签名差异;例如如果您更改了其中一个 POM 文件中的依赖项版本。

  6. 有效构建类路径的差异可能会导致找到导入类的顺序不同。这可能反过来导致类文件的常量池中的条目顺序出​​现不显着的差异。这可能是由于以下原因造成的:

    • 文件在外部 JAR 文件的目录中以不同的顺序出现,
    • 由于在您的构建工具迭代时源文件的顺序不同,文件以不同的顺序编译2,或
    • 构建中的并行性(如果已启用)。

对于文件排序问题有一个可能的解决方法:使用JDK-7003006 中描述的未记录的-XDsortfiles 选项。 (感谢@Holger 知道这一点。)

请注意,您通常看不到文件系统目录中文件的实际顺序。 lsdir 等命令行工具和文件浏览器通常会在显示条目之前对条目进行排序(按名称或时间戳顺序)。


1 - 这取决于编译器。此外,不能保证javap 会显示时间戳......如果它们存在的话。

2 - 操作系统不保证列出目录(在系统调用级别)将以确定的顺序返回文件系统对象...或相同的顺序,如果您已删除并重新添加文件.


我应该补充一点,确定差异原因的第一步是弄清楚它们究竟是什么。您可能需要(需要)以艰难的方式做到这一点 - 通过手动解码一对类文件来识别它们实际不同的地方......以及这些差异的实际含义。

【讨论】:

  • Stephen,我在反编译中看不到第 1,2 项的证据,第 3 项和第 4 项不适用于此处(来源,包括 POM,请记住)。但是,我认为第 5 项是可行的,因为两种情况下编译器编译的顺序可能不同。
  • 谢谢斯蒂芬。我认为第 5 项及以后的内容回答了我的问题。
【解决方案2】:

当您使用超越比较进行比较时,比较是根据文件的内容进行的。但是在构建过程中,只检查源文件的时间戳是否有变化。因此,如果您的源文件的最后修改日期发生更改,它将被重新编译。

【讨论】:

  • 我应该澄清一下,所有文件都是为两组源编译的(即在编译之前不存在类文件)
【解决方案3】:

不同的 JDK 产生不同的二进制类(优化,还有类版本号)。也有编译选项(JDK 可能以旧格式编译,或者它可以添加调试信息)。

【讨论】:

  • 我已经编辑以确认相同的 JDK 和编译选项等。
【解决方案4】:

不同版本的 Java 可以添加不同的元数据,这些元数据通常会被反编译器忽略。

我建议您尝试使用javap -c -v 来获取文件中的更多详细信息。如果这没有帮助,您可以使用查看每个字节的 ASMifierClassVisitor。

【讨论】:

    【解决方案5】:

    同样的 JDK 也可以有不同的输出,这取决于你的编译方式。 您可以使用或不使用调试信息进行编译,您可以编译以在旧版本中运行,每个选项都会导致其他类。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-11-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-02-20
      • 2014-09-28
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多