如果你是一名 Java 开发人员,你肯定指定 Java 代码有很多种不同的运行方式。比如说可以在开发工具(IDEA、Eclipse等)中运行,可以双击执行 jar 文件运行,也可以在命令行中运行,甚至可以在网页(比如各种 OJ)中运行。当然,这些执行方式都离不开 JRE(Java 运行时环境)。
JRE 包含运行 Java 程序的必需组件,包括 JVM(Java 虚拟机)以及 Java 核心类库等。Java 程序员经常接触到的 JDK(Java 开发工具包)同样包含了 JRE,并且还附带了一系列开发、诊断工具。
本篇文章主要针对以下两个问题和大家一起探讨:
- 为什么需要 JVM?
- 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