Java为什么解释执行时不直接解释源码?

为什么执行Java程序必须先用Java源码编译器(例如javac)编译为Java字节码,然后在用JVM执行Java字节码;就不能直接输入源码就得到执行结果么?

答案是:当然可以。分离的编译-执行模型是可以封装的。
完全可以写一个程序接受Java源码输入,内部悄悄调用javac把源码编译为字节码,然后交给JVM去执行得到结果。事实上通过Java SE 6开始提供的Java Compiler API非常容易在Java里实现这点,都不必调用外部的“javac”命令。
像Ruby(CRuby 1.9或以上)、Python(CPython),内部都是先把源码编译到字节码然后再解释执行的,但从用户的角度看就只有一个ruby / python命令,并没有分离的编译-执行步骤,看似是“直接解释执行源码”。

======================================================================================

这是一个“语言处理器”(language processor)的话题。
用于执行编程语言的语言处理器,从解释器到编译器这两个极端之间有整个系列的选择。

Java作为一种“古老”的编程语言,实现其执行的语言处理器也有全系列可选。

其中一个是DynamicJava,它就是一种源码解释器,直接在Java源码上解释执行而不编译到Java字节码再解释执行。具体说它是先把Java源码通过词法+语法分析转换为抽象语法树(AST)之后再在抽象语法树上做解释执行的:TreeInterpreter

从Java 9开始,Oracle JDK / OpenJDK将自带一个“jshell”命令,同样可以直接解释执行Java源码。

从用户的角度看,源码进去,执行结果就出来了,中间经过了怎样的步骤其实都不重要 ;-)

回到“全系列”的选择,那到底有些什么选择呢?
我们可以从一个比较简单的编译器的处理步骤看起:

编译流程:
  源码 [字符流]
- 词法分析 -> 单词(token)流
- 语法分析 -> 语法树 / 抽象语法树
- 语义分析 -> 标注了属性的抽象语法树
- 代码生成 -> 目标代码

执行流程:
  目标代码
- 操作系统/硬件 -> 执行结果

这描述的是一个分离的编译-执行流程:编译生成目标代码,目标代码持久化到例如磁盘上,然后执行时把目标代码再加载起来并执行出结果。
(注:这里假定目标代码是硬件可以直接执行的机器码)

在上面的流程中,我们可以从后向前逐步把处理融合起来。每融合一个处理步骤,在“执行”之前的处理部分看起来就更少更简单了一些,但在“执行”时要做的冗余动作就更多了一些。

例如说我们可以不要求用分离的编译-执行流程,而是直接在编译出目标代码之后让目标代码直接放在内存里,然后直接让硬件开始执行目标代码:

编译+执行流程:
  源码 [字符流]
- 词法分析 -> 单词(token)流
- 语法分析 -> 语法树 / 抽象语法树
- 语义分析 -> 标注了属性的抽象语法树
- 代码生成 -> 目标代码
- 操作系统/硬件 -> 执行结果

与之前的分离流程相比,这里从输入源码到得到执行结果只有一步,从使用角度看似乎简单了一些,但同时也意味着每次重新执行同样的源码都必须重新经过从源码到生成目标代码之间的编译流程,冗余变多了。

然后我们可以进一步从后向前融合,不生成目标代码,而是让程序维持在一种中间形式上就开始解释执行。例如说:

编译+解释执行流程:
  源码 [字符流]
- 词法分析 -> 单词(token)流
- 语法分析 -> 语法树 / 抽象语法树
- 语义分析 -> 标注了属性的抽象语法树
- 不做类型检查的抽象语法树解释器 -> 执行结果

这里我们通过实现一个能在硬件上执行的抽象语法树解释器(AST interpreter,或者就叫tree interpreter)来实现源程序的执行。
要留意的是:由于在解释执行前做了语义分析(其中包括但不限于类型检查),我们可以相信输入到解释器的抽象语法树的类型是正确的,所以解释器里不必重复做类型检查。
其它可能在语义分析阶段做的处理诸如:

  • 变量的确定性赋值:变量必须在使用前先得到初始赋值;
  • 变量的确定性不重复赋值:不可变变量(例如Java的final变量)最多只能被赋值一次
  • 控制流的正确性校验:例如Java的continue语句只能用在循环体内、continue的跳转标签只能向更外围作用域而不能向更深的嵌套作用域跳转,等等;

在解释执行之前做好这些分析,就意味着在解释执行过程中完全不必关心这些检查,因而解释执行的效率就可以更高。

然后可以进一步去掉解释执行前的语义分析,变为:

编译+解释执行流程:
  源码 [字符流]
- 词法分析 -> 单词(token)流
- 语法分析 -> 语法树 / 抽象语法树
- 需要做类型检查的抽象语法树解释器 -> 执行结果

没有了解释执行前的语义分析,要维持语言的语义正确,就必须在解释执行过程中融入语义分析本来应该完成的动作。例如:

  • 在看到一个“赋值”动作时,必须检查赋值目标(“左手边”)
    • 在作用域内是否存在
    • 类型是否匹配
    • 是否是final变量并且已经得到过赋值
    • ⋯等等
  • 在运行时必须维护一个“循环嵌套栈”,在执行“continue语句”时必须检查当前是否在循环里,并且要动态查找continue的跳转目标
  • ⋯等等

这些解释执行时做的语义分析的结果都不会被保存下来,所以多次执行到同一块代码时就得重复做这些分析。
这样,同样是在抽象语法树上解释执行,这个解释器就比上一个版本的解释器要重复做更多处理,因而会更复杂以及更慢。

我们可以进一步把语法分析也融合到解释执行中,变为:

编译+解释执行流程:
  源码 [字符流]
- 词法分析 -> 单词(token)流
- 需要做语法分析+类型检查的单词流解释器 -> 执行结果

此时解释器就不是在抽象语法树,而是在单词流上做解释执行了。为了保证我们只接受符合语法规则的程序,我们还是得做语法分析——只是把它融合到了解释器里而已。
与上一个版本的解释器最大的不同时,这个版本在解释器不会保留语法树/抽象语法树,所以解释器会一边做语法分析一边解释执行,如果多次执行同一块代码就得重复做语法分析。

最后,我们可以把词法分析也融合到解释执行中:

解释执行流程:
  源码 [字符流]
- 需要做词法分析+语法分析+类型检查的字符流解释器 -> 执行结果

有了前面的讲解,相信这一步是怎么回事不必多说了。

要实现一门编程语言,上面说的所有可能性都可以实现“执行”这一目标,但是从运行效率上看明显大有不同。

使用解释器实现的编程语言实现里,通常:

  • 至少会在解释执行前做完语法分析,然后通过树解释器来实现解释执行;
  • 兼顾易于实现、跨平台、执行效率这几点,会选择使用字节码解释器实现解释执行。

 

为什么大多数解释器都将AST转化成字节码再用虚拟机执行,而不是直接解释AST?

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Java 不是解释型语言,而是编译型语言。Java 代码首先被编译成字节码,然后在运行Java 虚拟机(JVM)解释执行。这种方式使得 Java 具有跨平台的特性,因为不同平台只需要安装相应的 JVM 即可运行相同的 Java 代码。 ### 回答2: Java解释型语言的原因有以下几点: 1. Java源代码需要通过编译器将其转换成中间字节码文件,而不是机器码。这些字节码文件可以在任何平台上运行,因此Java是一种跨平台语言。字节码文件并不包含特定于某个平台的指令,而是由Java虚拟机(JVM)在运行解释为机器码。 2. Java采用了解释加编译的混合执行方式。在运行,JVM会对字节码进行解释并逐行执行,这样可以实现动态的内存管理和垃圾回收机制。另一方面,JVM也会对频繁执行的字节码进行编译优化,将其转换成本地机器码以提高执行速度。 3. 解释型语言的特点之一是更加灵活和可移植。Java解释执行过程可以在运行动态地加载和更新类、接口等结构,从而实现灵活的扩展和更新,而无需重新编译整个程序。 4. 解释型语言的另一个优点是易于调试和错误处理。由于Java源代码和字节码之间存在一定的映射关系,JVM可以提供更详细的错误信息和调试功能,使得开发人员能够更容易地定位和解决问题。 总结起来,Java解释型语言主要是因为它将源代码编译为字节码,然后通过JVM解释执行。这种执行方式使得Java具有跨平台、灵活、可扩展和易于调试等特点。 ### 回答3: Java是一种混合了解释型和编译型特性的语言。Java代码首先经过编译器将源码编译成字节码文件,然后通过Java虚拟机(JVM)将字节码解释为机器码并执行。所以可以说Java是半解释型语言。 Java选择解释型语言的原因有多个方面。首先,Java的平台无关性是其主要优势之一,解释型语言可以在不同的平台上运行。通过字节码的解释,JVM可以将Java程序在各种操作系统上执行,而不需要重新编译。这为Java程序的跨平台开发和移植性带来了极大的便利。 其次,解释型语言使得Java程序更容易调试和测试。由于解释型语言可以逐行执行代码,开发者可以在运行检查变量的值、检测问题并通过修改代码进行即的调试。这样可以大大提高开发效率和程序质量。 另外,解释型语言的动态特性也是Java选择解释型语言的原因之一。Java程序可以在运行进行属性和方法的动态绑定,允许程序在运行过程中进行添加、替换和移除等操作。这为Java提供了丰富的扩展能力和动态性。 虽然Java解释型语言,但是为了提高程序的性能,Java的字节码可以被即编译器(JIT)编译成机器码,以进行更快速的执行。这使得Java执行速度上与其他编译型语言相媲美。 综上所述,Java之所以选择解释型语言的方式,是为了实现跨平台、提高开发效率、增加动态特性和保持较高的执行性能。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值