如果你是一名 Java 开发人员,你肯定指定 Java 代码有很多种不同的运行方式。比如说可以在开发工具(IDEA、Eclipse等)中运行,可以双击执行 jar 文件运行,也可以在命令行中运行,甚至可以在网页(比如各种 OJ)中运行。当然,这些执行方式都离不开 JRE(Java 运行时环境)。

JRE 包含运行 Java 程序的必需组件,包括 JVM(Java 虚拟机)以及 Java 核心类库等。Java 程序员经常接触到的 JDK(Java 开发工具包)同样包含了 JRE,并且还附带了一系列开发、诊断工具。

本篇文章主要针对以下两个问题和大家一起探讨:

  1. 为什么需要 JVM?
  2. JVM 是怎样运行 Java 代码的呢?

为什么需要 JVM?

Java 一个非常重要的特点就是与平台的无关性,而使用 JVM 是实现这一特点的关键。Java 作为一门高级程序语言,语法复杂,抽象程度高。因此,直接在硬件上运行这种复杂的程序并不现实。所以在运行 Java 程序之前,我们需要对其进行转换。

设计一个面向 Java 语言特性的虚拟机,并通过编译器将 Java 程序转换成该虚拟机所能识别的指令序列(因为 Java 字节码指令的操作码(opcode)被固定为一个字节,故又称 Java 字节码)。

JVM 一般是在各个现有平台(如 Windows、Linux)上提供软件实现,这样可以使一旦一个程序被转换成 Java 字节码,那么便可以在不同平台上的虚拟机实现里运行(一次编写,到处运行)。

JVM 另外一个好处是带有托管环境(Managed Runtime),托管环境能够代替处理一些代码中冗长而且容易出错的部分,其中包括自动内存管理与垃圾回收(GC)。

另外,托管环境还提供了诸如数组越界、动态类型、安全权限等等的动态检测,使我们免于书写这些无关业务逻辑的代码。

JVM 是怎样运行 Java 代码的呢?

JVM 具体是怎么运行 Java 字节码的呢?下面我们一起来看一下:

从 JVM 来看,执行 Java 代码首先需要将它编译而成的 class 文件加载到 JVM 中。加载后的 Java 类会被存放于方法区(Method Area)中。实际运行时,JVM 会执行方法区内的代码。

JVM 会在内存中划分出堆和栈来存储运行时数据,JVM 会将栈细分为面向 Java 方法的 Java 方法栈,面向本地方法(用 C++ 写的 native 方法)的本地方法栈,以及存放各个线程执行位置的 PC 寄存器。

在运行过程中,每当调用进入一个 Java 方法,JVM 会在当前线程的 Java 方法栈中生成一个栈帧,用以存放局部变量以及字节码的操作数。栈帧的大小是提前计算好的,而且 JVM 不要求栈帧在内存空间里连续分布。

当退出当前执行的方法时,不管是正常返回还是异常返回,JVM 均会弹出当前线程的当前栈帧,并将之舍弃。

从硬件视角来看,Java 字节码无法直接执行。因此,JVM 需要将字节码翻译成机器码。

在 HotSpot 里面,上述翻译过程有两种形式:第一种是解释执行(interpre