Java 的平台无关性(一次编译,到处运行)如何实现?
Java 的平台无关性(一次编译,到处运行)如何实现?
总所周知,通常把 Java 分为编译时和运行时。
对于编译时会使用到 javac
指令,将源代码编译生成字节码,并存储到对应的 .class
文件之中。举一个例子:
首先先在 IDEA 中创建一个项目创建包后编写以下代码以便进行后续的操作:
package cn.xilikeli.javabasic.bytecode;
/**
* <p>
* 示例代码
* </p>
*
* @author 踏雪彡寻梅
* @version 1.0
* @date 2020/8/9 - 21:39
* @since JDK1.8
*/
public class ByteCodeSample {
public static void main(String[] args) {
int i = 1;
i++;
System.out.println(i);
}
}
编写好代码之后,通过使用 javac
指令对该源码文件进行编译,如下图所示:
从上图中,可以看到这个类编译之后,就产生了一个 .class
文件。
对于 .class
文件,其中保存着 .java
文件翻译成的二进制字节码,也就是说 Java 类文件中的属性、方法以及类中的常量信息等都会分别存储到 .class
文件中。除此之外还会添加一个公有的静态常量属性 .class
,这个属性记录了类的相关信息,即类型信息,是 Class 类的一个实例。
class 文件存在的意义就是: 跨平台,即它为跨平台的基础。各种不同平台的虚拟机都统一使用这种相同的程序存储格式。不同平台的 JVM 解析相同的 .class
文件在不同平台上运行。
所以以上源文件编译之后生成 .class
文件之后,就可以使用 java
指令让 JVM 去解析对应的 class 文件,将里面的字节码内容加载进内存,最终转换成本操作系统能够识别的机器码去运行程序了,如下图所示:
那么 class 文件中到底放了什么东西呢,可以通过使用 javap
指令反编译 .class
文件查看其内容,javap
是 JDK 自带的反汇编器,可以查看编译期产生的字节码,我们可以通过比较字节码和源代码,了解很多知识,例如可以了解编译器内部工作的机制,如下图所示:
上图中显示的这些指令,是真正加载 class 文件去执行时用到的虚指令。通过这些指令可以了解到程序的具体细节。这里就不再阐述了。
对于这个 class 文件,如果在其他平台上直接使用 java
指令去执行也是可以的,这也体现出了跨平台的特性,如下图所示(下图的环境为 Linux,上面示例的环境为 macOS):
了解了以上知识之后,就可以回到 Java 的平台无关性(一次编译,到处运行)如何实现 这个问题了。
通过上面的示例,对于这个问题就可以这样解释:
Java 源码首先被编译成字节码,再由不同平台上的 JVM 进行解析,编译之后在不同的平台上运行时不需要进行重新编译,Java 虚拟机在执行字节码的时候,把字节码转换成具体平台上的机器指令进行执行。
对于上面的问题, 还可以衍生出这个问题: 为什么 JVM 不直接将源码解析成机器码去执行呢?
对于这个问题,可以这样看待,如果直接将源码解析成机器码去执行,那么每次执行都需要各种语法之类的检查,即检查结果不会被保留下来,都要重新编译重新分析,整体性能会受影响,做很多重复的工作。所以引入了中间字节码,能够保证在编译成字节码之后,多次执行程序不需要再经过重复的各种检查校验。