Java解释器或任何解释器是如何工作的?

11

我一直在研究解释器的精确工作方式,已经在谷歌上搜索了相关信息并得出了一些结论,只是希望有人能够更好地理解解释器的工作方式,并纠正我的理解。

我现在的理解是:

  1. 解释器是将高级语言代码转换为机器格式的软件程序。
  2. 就Java解释器而言,它接收二进制格式的代码(这些代码之前是由Java编译器从源代码转换为字节码)。
  3. 现在Java解释器的平台是JVM,在JVM中运行,因此基本上是要生成可以在JVM上运行的代码。
  4. 所以它会获取字节码产生中间代码和目标机器代码,然后将其交给JVM。
  5. JVM反过来在实现或运行JVM的操作系统平台上执行该代码。

现在我仍然不清楚中间发生的子过程,即:

  1. 解释器生成中间代码。
  2. 解释代码然后进行优化。
  3. 然后生成目标代码。
  4. 最后执行。

还有一些问题:

  • 因此,解释器是否单独负责生成目标代码?以及执行它?
  • 执行是否意味着在JVM中执行,还是在底层操作系统中执行?

你可能想查看这个答案以获取有关解释器实际工作原理的更多细节:https://stackoverflow.com/questions/56823223/how-native-code-is-converted-into-machine-code-in-jvm/56834686#56834686 - Juraj Martinka
请记得在适当的情况下接受其中一个答案。 - Gray
5个回答

14
一个解释器是一种将高级语言代码转换为机器格式的软件程序。不是,那是编译器。一个解释器是一种直接执行语言指令的计算机程序。这与将高级语言转换为低级语言的编译器不同。C编译器从C到汇编代码,而汇编程序(另一种类型的编译器)则从汇编到机器代码 - 现代C编译器可以完成从C到机器代码的两个步骤。在Java中,java编译器执行代码验证并将Java源代码转换为字节码类文件。它还执行许多小处理任务,例如常量的预计算(如果可能),字符串的缓存等。现在Java解释器的平台是JVM,在其中运行,因此基本上它将生成可以由JVM运行的代码。
JVM直接操作字节码。Java解释器与JVM的整合非常紧密,它们不应被视为分离实体。此外,也存在大量优化,使得字节码可以在执行时动态优化,仅仅称之为解释器是不够准确的。请参考下面的内容。
“因此,它接收字节码并生成中间代码和目标机器代码,并将它们提供给JVM。”
JVM负责这些转换。
“我更愿意说JVM使用字节码、经过优化的用户代码、包括Java和本地代码的Java库以及OS调用来执行Java应用程序。”
“现在我还不清楚其中的子进程,即1.解释器生成中间代码。2.解释的代码随后进行优化。3.然后生成目标代码。4.最后执行。”
Java编译器生成字节码。当JVM执行代码时,步骤2-4在JVM内部的运行时发生。这与C(例如)非常不同,因为这些单独的步骤由不同的实用程序运行。不要将其视为“子进程”,而应将其视为JVM内部的模块。
“那么解释器是否负责生成目标代码?并执行它?”
有点像。JVM的解释器根据定义读取字节码并直接执行它。但是,在现代JVM中,解释器与即时编译器(JIT)一起工作,以便动态生成本机代码,以使JVM能够更有效地执行您的代码。
此外,还有后处理的“编译”阶段,它会在运行时分析生成的代码,以便通过其他机制优化本机代码,例如内联经常使用的代码块。这就是为什么JVM在启动时负载峰值如此之高的原因。它不仅加载jar和class文件,而且实际上正在进行cc -O3的即时编译。
尽管我们谈论JVM执行代码,但这在技术上并不正确。一旦字节码被翻译成本地代码,JVM和Java应用程序的执行就由CPU和其他硬件架构完成。操作系统是底层基础,负责所有进程和资源管理,以便程序可以高效地共享硬件和执行任务。操作系统还提供API,使应用程序可以轻松访问磁盘、网络、内存和其他硬件和资源。

1
感谢您提供如此好的解释,但是这个主题非常广泛且相当令人困惑,我正在花时间去理解它。另外,在C语言中,汇编语言被汇编器编译成机器码,因此在JAVA中,字节码可以被视为(仅作为例子)等同于汇编代码,然后由Java解释器将其转换为机器码并执行。 - Rajan Chauhan
1
C编译器是一个编译器,而不是解释器。它可以将代码编译成汇编代码,然后再翻译成本地代码。或者,它也可以直接编译成机器码。 - Stephen C
3
“字节码可以被看作(仅举例)相当于汇编代码,然后由Java解释器转换为机器代码,然后执行。” 不!解释器并没有将其转换为机器代码。它直接运行/执行字节码。(我们需要说几次?) - Stephen C
你一直试图使用C领域的术语来描述Java和JVM中发生的事情,但它们并不适用或者有很大的区别,因此任何比较都会令人困惑。请参考我的和Stephen的答案以获取更多细节。 - Gray
@StephenC 嘿,老兄。感谢你的修改,但我有些不同意,所以我做了一些编辑。我已经链接到一些参考资料,介绍了解释器和JIT之间的差异在今天变得模糊的情况。请参见:https://www.whizlabs.com/blog/what-is-just-in-time-compiler-difference-between-compiler-and-interpreter/ 和 https://www.quora.com/Is-Java-a-compiled-language-or-interpreted-What-is-the-difference-What-is-the-JIT-compiler - Gray

8

1) 解释器是一种软件程序,将高级语言的代码转换为机器格式。

不正确。解释器是一种运行以某种语言表达的程序的程序,该程序不是计算机的本机机器代码。

在这个过程中,可能存在一步将源语言解析并翻译为中间语言的过程,但这不是解释器的基本要求。在Java的情况下,字节码语言已被设计为不需要解析或独立的中间语言。

2) 具体来说,关于java解释器,它会获得二进制格式的代码(该代码早先由java编译器从源代码转换为字节码)。

正确的。 "二进制格式" 是Java字节码。

3) 现在,java解释器的平台是JVM,在JVM中运行,所以基本上它会产生可以由JVM运行的代码。

不正确。字节码解释器是JVM的一部分。解释器不在JVM上运行。并且字节码解释器不生成任何内容。它只是运行字节码。

4) 因此,它获取字节码生成中间代码和目标机器代码,并将其提供给JVM。

不正确。

5) JVM转��在JVM实现或运行的操作系统平台上执行该代码。

不正确。

真正的故事是这样的:

  • JVM具有许多组件。
  • 一种组件是字节码解释器。它几乎直接执行字节码。1可以将解释器视为抽象计算机的仿真器,其指令集为字节码。
  • 第二个组件是JIT编译器。它将字节码转换为目标机器的本机机器代码,以便可以通过目标硬件执行。

1-典型的字节码解释器会做一些工作,将抽象堆栈帧和对象布局映射到涉及特定目标大小和偏移量的具体堆栈帧和对象布局中。但是将其称为"中间代码"是一种拉伸。解释器实际上只是增强了字节码。


回复:“字节码解释器不会产生任何东西。它只是运行字节码。”这并不完全正确。JVM中使用的模板解释器实际上在JVM初始化阶段为每个操作码生成机器代码。然后将其存储在代码缓存中,并由解释器执行。请参见https://metebalci.com/blog/demystifying-the-jvm-jvm-variants-cppinterpreter-and-templateinterpreter - Juraj Martinka

8

简述一下,希望能够澄清问题:

Java应用程序有两个主要步骤:编译运行时。每个过程都有非常不同的功能和目的。以下是它们的主要过程:

编译

  • 这通常由[com.sun.tools.javac][1]执行,通常可以在tools.jar文件中找到,传统上位于$JAVA_HOME - 与java.jar等相同的位置。
  • 这里的目标是将.java源文件转换为包含java运行时环境“配方”的.class文件。

编译步骤:

  1. 解析:读取文件,并去除其“边界”语法字符,例如花括号、分号和括号。这些存在于告诉解析器将每个源组件翻译成哪个Java对象的语法中(关于此更多内容请参见下一点)。
  2. AST创建:抽象语法树是源文件的表示方式。它是一个字面上的“树”数据结构,其根类为[com.sun.tools.JCTree][3]。总体思路是每个表达式和语句都有一个Java对象来表示。在这个时候,关于实际“类型”的了解还很少。在AST创建时唯一检查的是字面语法。
  3. 去糖:这是将for循环和其他语法糖转换为更简单形式的过程。语言仍处于“树”形式,而不是字节码,因此可以很容易地进行转换。
  4. 类型检查/推断:编译器变得复杂的地方。Java是一种静态语言,因此编译器必须使用访问者模式遍历AST,提前确定每个东西的类型,并确保在运行时所有(几乎所有)类型、方法签名等都是合法的。如果某些内容过于模糊或无效,则编译失败。
  5. 字节码:检查控制流以确保程序执行逻辑有效(没有不可达语句等)。如果所有检查都没有错误,则将AST转换为程序表示的字节码。
  6. .class文件写入:此时,类文件已经被写入。实质上,字节码是专门的机器代码上的一个小抽象层。这使得可以将其移植到其他机器/CPU结构/平台上,而无需担心它们之间的相对较小的差异。

运行时

  • 每个计算机平台都有不同的运行时环境/虚拟机实现。Java API是通用的,但运行时环境是完全独立的软件。
  • JRE 只知道如何将类文件中的字节码转换为与目标平台兼容的机器代码,并且这也高度优化了各自的平台。
  • 有许多不同的运行时/虚拟机实现,但最受欢迎的是Hotspot VM。
  • VM非常复杂,可以在运行时优化您的代码。启动时间很慢,但它基本上是“学习”过程。
  • 这是'JIT'(即时)概念的实现 - 编译器通过检查正确的类型和语法来完成所有繁重的工作,而VM只需在进行过程中将字节码转换和优化为机器代码。

还有...

  • Java编译器API已经在JSR 199下得到标准化。尽管不完全属于同一范畴(找不到确切的JLS),但许多其他语言和工具利用标准化的编译过程/API,以便使用Oracle提供的先进JVM(运行时)技术,同时允许使用不同的语法。
    • 请参见Scala, Groovy, Kotlin, Jython, JRuby等。所有这些都利用Java Runtime Environment,因为它们将其不同的语法转换为与Java编译器API兼容的形式!这非常棒 - 任何人都可以编写高性能语言,并使用他们想要的任何语法,因为这两者是分离的。几乎每种语言都有适应JVM的版本。

嗨,普雷斯顿,对我来说这是一个十万英尺的挑战,因为我刚开始学习Java架构,但你的帮助真的很大。 - Rajan Chauhan

2

根据我创建DSL的经验,我来回答这个问题。

C是编译型语言,因为你需要将源代码传递给gcc并运行存储在机器码中的程序。

Python是解释型语言,因为你需要通过将程序源代码传递给解释器来运行程序。解释器读取源文件并执行它。

Java是两者的混合,因为你需要将Java文件“编译”成字节码,然后调用JVM来运行它。字节码不是机器码,需要由JVM解释。Java处于C和Python之间的一个级别,因为你不能像Python那样做一些花哨的事情,比如“eval”(在运行时评估代码块或表达式)。但是,Java具有C程序无法实现的反射能力。简而言之,Java运行时设计处于纯编译语言和解释语言之间的中间层,以性能和灵活性方面提供了最佳(也是最差)的两种语言特点。

然而,Python也有自己的虚拟机和字节码格式。Perl、Lua等语言的解释器也是如此。这些解释器首先将源文件转换为字节码,然后再解释字节码。

我一直想知道为什么要这样做,直到我为一个模拟DSL创建了自己的解释器。我的解释器进行词法分析(将源代码分解为标记),将其转换为抽象语法树,然后通过遍历该树来评估它。出于软件工程的考虑,我使用了一些设计模式,并且我的代码大量使用多态性。与处理类似于实际计算机体系结构的高效字节码格式相比,这非常慢。例如,对于评估长数字表达式,将其转换为类似于汇编代码的东西要比处理抽象树的分支快得多,因为它需要调用许多多态方法。


1
有两种执行程序的方式。
一种是通过编译器:它将编程语言中的文本(例如.c)解析成机器代码,在Windows上为.exe。然后可以独立于编译器执行。
这种编译可以通过将多个.c文件编译为多个对象文件(中间产品),然后将它们链接到单个应用程序或库中来完成。
另一种是通过解释器:它解析编程语言中的文本(例如.java)并“立即”执行程序。
对于java,这种方法有点混合/堆叠:java编译器javac.java编译为.class文件,并将其压缩为.jar(或.war.ear等)。.class文件包含更抽象的字节码,适用于抽象堆栈机器。
然后,Java运行时环境(称为JVM、Java虚拟机或字节码解释器)可以执行.class/.jar文件。实际上,这是Java字节码的解释器。现在它还能够在运行时将字节码的部分翻译成机器码。这也被称为将字节码即时编译成机器码的即时编译器。
简而言之: - 编译器 只是创建代码; - 解释器 立即执行。
解释器将循环解析命令/高级中间代码,并使用一段代码解释每个命令。间接且原则上较慢。

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