Java - 二进制代码和字节码是否相同?

34
在Java中,“binary code”是否意味着与“Java bytecode”相同?
这是Java的流程吗?

Java文件(.java) -> [javac] -> 字节码文件(.class)- > [JVM/Java解释器] -> 运行它(首先将其转换为特定于机器的二进制代码)

谢谢!

6
你在什么情境下看到了“二进制代码”这个术语? - templatetypedef
3
一切都是“二进制代码”!糟糕!! - David Titarenco
1
请注意,在大多数真实世界的JVM上,Java并不像真正的解释性语言那样被解释。首先(在任何JVM上都是如此),它不是源代码而是字节码被“解释”(仅这一点就已经构成了一个非常奇怪的“解释”的定义,因为它比源代码解释更高效)。然后,大多数真实世界的JVM都是“JIT”。JIT意味着编译。这使得“解释”的定义/用法变得更加奇怪。 - SyntaxT3rr0r
@SyntaxT3rr0r:这一点并不具有误导性:http://java.sun.com/docs/white/langenv/Intro.doc2.html - 阅读1.2.5。JVM进行了一些非常聪明的JIT技巧,以产生可以与C和C++竞争的性能,但这并不意味着字节码没有被解释,因为它确实被解释了。当Java进行JIT时,它还需要预热(通常在性能关键代码块之前进行几个解释周期,然后切换到JIT)。 - David Titarenco
@David Titarenco:事实上,能够运行本地Java字节码的CPU说明Java字节码离底层非常近。因为它的字节码在许多架构上具有将其指令一一映射到机器代码的一对一映射,所以称Java为解释型语言是高度误导性的。我知道Sun公司将其命名为这个名称,但我认为他们决定使用这个愚蠢的名称非常遗憾,因为它给Java带来了很坏的声誉以及无数毫无意义、缺乏信息的“Java是解释型语言”的评论。重要的代码是即时编译(JIT)的,并不再被解释。 - SyntaxT3rr0r
显示剩余6条评论
6个回答

40
答案取决于你对二进制代码的理解。 Java字节码是一种二进制数据格式,包括Java虚拟机的加载信息和执行指令。在这个意义上,Java字节码是一种特殊类型的二进制代码
当你使用术语“二进制代码”指代真实处理器架构(如IA-32或Sparc)的机器指令时,它是不同的。Java字节码在这个意义上不是一个二进制代码。它不是特定于处理器的。

14

JVM是一个非常复杂的程序,其中的流程在某种程度上是不可预测的。例如,在HotSpot JVM内部的流程大致如下:

1)它获取您的字节码并解释执行
2)如果某个方法被频繁执行(在某个时间段内执行了一定次数),则将其标记为“热”方法,并且JVM安排将其编译成平台相关的机器代码(这就是您所说的二进制代码吗?)。该流程如下所示:

ByteCode
--> Hige-level Intermediate Representation (HIR)
  --> Middle-level Intermediate Representation (MIR)
    --> Low-level Intermediate Representation (LIR)
      --> Register Allocation
        --> EMIT (platform dependent machine code)

这个流程中的每一步都很重要,有助于JVM对您的代码进行一些优化。当然,这不会改变您的算法,优化只是意味着可以检测到某些代码序列并用性能更好的代码进行替换(产生相同的结果)。从LIR阶段开始,代码变得依赖于平台(!)。

字节码可能适合解释,但不足以轻松转换成机器本地代码。HIR负责此事,其目的是将字节码快速转换为中间表示。MIR将所有操作转换为三操作数操作;ByteCode基于堆栈操作:

iload_0
iload_1
iand

这是简单的and操作的字节码,对应的中间表示形式如下:

and v0 v1 -> v2

LIR依赖于平台,在考虑我们使用and操作的简单示例并将我们的平台指定为x86时,我们的代码片段将如下所示:

x86_and v1 v0 -> v1
x86_move v1 -> v2

因为and操作需要两个操作数,第一个是目标,另一个是源,然后我们将结果值放入另一个"变量"中。下一阶段是"寄存器分配",因为x86平台(以及可能大多数其他平台)使用寄存器而不是变量(如中间表示)或堆栈(如字节码)。在这里,我们的代码片段应该像以下内容:

x86_and eax ecx -> eax

在这里,您可以注意到缺少“移动”操作。我们的代码只包含一行,JVM发现不需要创建新的虚拟变量;我们可以重复使用eax寄存器。如果代码足够大,有许多变量并且对它们进行了密集的工作(例如在下面某个地方使用eax,因此我们无法更改其值),则会在机器代码中保留移动操作。这又涉及到优化问题 :)
那是JIT流程,但根据VM实现,可能还有一个步骤 - 如果代码已编译(处于“热”状态),并且仍然执行了很多次,则JVM会安排对该代码进行优化(例如使用内联)。
总之,从字节码到机器码的路径非常有趣,有点不可预测,并取决于许多因素。
顺便说一下,上述描述的过程称为“混合模式解释”(当JVM首先解释字节码,然后使用JIT编译时),这样的JVM示例是HotSpot。一些JVM(例如来自Oracle的JRockit)仅使用JIT编译。
这是对正在进行的事情的非常简单的描述。我希望它能帮助您在非常高的层次上理解JVM内部的流程,以及针对字节码和二进制代码之间的差异的问题。有关参考资料和其他未在此处提及且与该主题相关的问题,请阅读类似主题“为什么编译的Java类文件比C编译的文件小?”。
同时,欢迎批评这个答案,指出我的错误或误解,我始终愿意改进自己对JVM的知识 :)

@magnetar:谢谢,很高兴听到这不是浪费时间 ;) - Maxym

7
“机器无关字节码”这种说法实际上是不存在的(如果你思考一下就会发现没有意义)。字节码只用于虚拟机等方面。虚拟机(比如JVM)会“解释”字节码,使用聪明而复杂的及时编译(这是与机器和平台有关的)来得到最终产品。
因此,从某种意义上来说,两个答案都对也都错了。Java编译器将代码编译成Java字节码(与机器无关)。字节码所在的*.class文件是二进制的,毕竟它们是可执行文件。虚拟机随后解释这些二进制的*.class文件(注意,当将文件描述为二进制时,这是有点误导性的),并做出各种很棒的事情。通常情况下,JVM使用称为JIT(即时编译)的东西,生成平台特定或机器特定的指令来加速执行的各个部分。但是,JIT是另一个话题,可以在以后再谈论。
Java File (.java) -> [javac.exe] -> ByteCode File (.class) -> [JVM/Java Interpreter] -> Running it(by first converting it into binary code specific to the machine)

这是不正确的。JVM并没有“转换”任何东西。它只是解释字节码。JVM中唯一“转换”字节码的部分是当调用JIT编译器时,这是一个特殊情况,不应该概括。


你刚才说,“JIT编译”是机器无关的。但我读过JVM对于每台机器都是特定的,不像ByteCode。 - user327663
JIT编译只是JVM工作的一小部分(也是“重新编写”特定机器/平台代码的唯一部分)。虚拟机是一个特定于平台/机器的解释器,但字节码不是。 - David Titarenco
1
Titerenco: 也许我没有理解你所说的“即时编译(它是机器/平台无关的)”,但 JIT 编译是与平台有关的,从流程的 LIR 阶段开始,它产生与平台有关的代码!编译的结果是本地平台相关的机器码,因此它不能是独立的……字节码当然是跨平台的…… - Maxym
@maxym:那是个打字错误,抱歉。JIT是依赖于平台的。 - David Titarenco

4

以 C/C++ 和 Java 为例,这两种编程语言的程序都会被编译成二进制代码。这个通用术语的意思是新创建的文件不会以人类可读的方式编码指令(也就是说,你无法在文本程序中打开已编译的文件并阅读它)。

另一方面,二进制的 0 和 1 所代表的内容(或表示的含义)取决于编译器生成的内容。在 Java 的情况下,它会生成称为字节码的指令,由 JVM 解释执行。而对于其他语言,则可能生成 IA-32 或 SPARC 指令。

总之,将二进制代码Java 字节码这两个术语相对立是具有误导性的。其原因是要区分机器相关的普通二进制代码和非机器相关的 Java 字节码(同样是二进制代码)。


1

我今天找到的答案如下:

来源:JLS

加载是指查找具有特定名称的类或接口类型的二进制形式的过程,可以通过实时计算进行,也可以更常见地通过从Java编译器先前从源代码中检索计算出来的二进制表示,并从该二进制形式构造一个Class对象来表示类或接口。

加载的精确语义在Java虚拟机规范第5章中给出,Java SE 7版。这里我们从Java编程语言的角度概述这个过程。

类或接口的二进制格式通常是《Java虚拟机规范》,Java SE 7版中描述的类文件格式,但其他格式也可能是可行的,只要它们符合§13.1中指定的要求。ClassLoader类的defineClass方法可以用于从类文件格式中的二进制表示构造Class对象。


0
谈论程序时,术语二进制代码通常表示以二进制形式编码的可执行程序(作为位序列编码)。换句话说,二进制代码是任何已编译的程序,而不是以文本形式分发和执行(解释)的脚本二进制代码可以分为两种:机器码和字节码。 机器码是按照实际硬件微处理器规格编码的程序。因此,它可以直接由目标微处理器执行,无需任何其他软件的介入。相比之下,字节码是按照某些虚拟微处理器(虚拟机)规格编码的程序。因此,它可以被解释或转换成机器码然后直接执行。
因此,每个字节码都是二进制代码,但并非每个二进制代码都是字节码。在您的问题上下文中,“Java字节码”无条件是“二进制代码”,但“二进制代码”未必是“Java字节码”,而可以是“Java字节码”。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接