这是我参与8月更文挑战的第 19 天,活动详情查看:8月更文挑战
我们会先来认识 JVM 是什么、有什么以及它能干什么。
是什么、有什么以及它能干什么,是咱们学习新技术,或者是认识一个新事物的一个基本学习思路或者学习方法。
比如当我们拿到新的东西,我们首先会想这是个什么东西,它有哪些部分构成,这个东西到底能干什么,以此来判断,这个东西对我们是有用还是没有用,或者我们是不是需要去使用它。 如果说确定我们要去使用它,那好,我们再来继续去研究它是怎么实现的呢?怎么做的呢?或者说再去研究我们该如何来使用它呢?
那么连起来就是:是什么,有什么,能干什么,以及怎么做。这是我们学习一个新技术或者认识新事物的一些基本的学习方法和思路。
另外我们再去看一下 Java 如何实现平台无关。
JVM 概述
JVM 是 Java Virtual Machine 的缩写,也就是我们耳熟能详的 Java 虚拟机,JVM是一种用于计算设备的规范。它能识别 .class后缀的文件,并且能够解析它的指令,最终调用操作系统上的函数,完成我们想要的操作。它是一个虚构的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。 所谓虚拟机,它指的是通过软件模拟的,具有完整硬件系统功能的,并且运行在一个完全隔离环境当中的,这样一个计算机系统。重点是一个软件模拟的,另外一个它是个计算机系统。也就是说我们认为虚拟机它本身可以看成是一个计算机系统,只不过呢他是有软件模拟的,它不是真实的物理存在的这么一个计算机系统,所以叫做虚拟机。
它的特点是具有完整硬件系统功能,并且运行在一个完全隔离的环境当中。那么这样的一个计算机系统,它上面就可以跑很多程序。当然我们是 Java 的虚拟机,当然面跑的就是咱们的 Java 应用程序。
JVM 是通过软件来模拟 Java 字节码的指令机,给大家一看指令集在真实的这个物理机器上,主要是 CPU 来执行指令集。在 JVM里面,它就通过软件来模拟这个指令集,JVM 是 Java 程序的运行环境。
我们写的这些 Java 应用程序,最终它都是运行在 JVM 之上的。也就是说 JVM 提供了我们 Java 程序的一个运行环境。
我们写的 Java 代码到底是如何运行起来的
接下来看一下下面的这张图。
Java 编译环境
这张图的左半部分,很明显的这是咱们的开发过程。首先我们是去编写了 Java 的源代码,就 .java 文件,然后通过 Java 编译器。比方说 javac 这样子的命令,或者利用 ide 的这个工具,把它编译成为这个 Java 字节码,就是 .class 文件。整个过程是的开发过程。
当我们把它编译成为 .class 文件过后,也就说是字节码文件过后。在运行期环境,也就就我们的虚拟机。首先会通过类装载器,就是 ClassLoader 来把我们的字节码通过本地或者是网络的方式。当然现在网络方式并不是很多了,像以前 Java 里面有个技术叫 applet,现在很少用了。总之了一句话,把我们的字节码文件通过内装载器,把它装载到虚拟机里面来。
当装载的过程当中,它肯定还要对这个字节码文件进行一些校验和认证。就比方验证字节码文件到底符不符合虚拟机的规范,格式到底是不是正确,等等的。
在这个装载的过程当中,还会去装载一些 Java 内裤,也就是说由虚拟机提供的一些基本的必要的支撑,比如基本的数据类型,基本的 if else、for 循环等等的,还有加载一些相关的处理,这些处理还包括一些基本的 Java 安全的处理,以及跟一些硬件的处理等等。就是说必须要有虚拟机来提供的这些功能,它也会在这个时候通过类装载器把这些内裤装载进去。 那当把这些需要的东西都装载到虚拟去过后,虚拟机就会把这些东西交给 Java 解释器,那么 Java 解释器,就会去对字节码进行解释并执行。整个这个过程就是指运行期的。
当然运行期比这复杂得多,当我们把 .class 交到去运行的时候,那么运行环境首先肯定就要去做一些基本的工作,比方说内存分配,分配的内存过后就要把这些字节码进行执行,就会涉及到字节码执行引擎。
在这个运行过程当中,这个内存当中产生垃圾,那要进行垃圾回收。如果这个里面会有很多涉及到并发的代码的话,那么一定会有高效的一些并发处理等。
当然这个运行期间还有其他安全处理等一系列的过程,这些都是运行期要做的事情,这个就是我们的 Java 虚拟机,它提供的就是我们的 Java class 文件运行的这么一个环境,就是从装载进来到解释执行整个过程。
应用需要去跟硬件或者操作系统交互的话,比如我们通过 JIT 就会我们的虚拟机,由虚拟机去跟操作系统进行交互,操作系统再跟硬件进行交互。
透过这个完整的过程,从编写代码到最后运行起来了这么一个流程,基本上就能看得到虚拟机在里面,它所处的位置以及它的一些主要的知识,就相当于我们透过这个去形成对虚拟机有基本了解,并且对它的知识体系形成一个知识大纲。
首先 Java 的字节码文件,是要遵循 JVM 的规范。里面肯定会对整个字节码文件会有一些详尽的定义。然后装载进来就说到这个地方,我们肯定要去有 ClassLoader,就说类的装载器才能把它装载到 JVM 里面来。
装载进来过后,当这个内裤我们就不说了,这里头了要开始真正执行的时候,运行期间要去做内存的分配。然后这个分配完了过后,我要解释执行这些代码指令集,那我们将用到字节码的执行引擎。那么执行过程当中,内存当中产生的一些垃圾,我们叫垃圾回收,这个过程当中涉及到一些并发的处理的话,那我们自然会需要高效的并发处理等等。
基本上就是一个虚拟机里面最主要的一些部分,这个都是需要大家后面一点一点详细的去学习的,那这里我们就不多说。
JVM 的主要功能
接下来我们一起来学习 JVM 的主要功能。
第一个就是通过 ClassLoader 来寻找和装载 class 文件。ClassLoader 如何去寻找,如何去装载等等的,一系列的过程。后面学 ClassLoader 的时候再来讲。当然像 ClassLoader 还有自定义这些东西。这里暂时就不多说,我们只要了解 JVM 首先第一件事情是通过这个 ClassLoader 来寻找和装载 class 文件,先把类文件装载进来。
第二个解释字节码成为指令并执行,就刚才提到的字节码的这个执行引擎,以提供 class 文件的运行环境。
第三个就是进行运行期间的内存分配,还有垃圾回收。
第四个就是提供与硬件交互的平台,透过这个其实我们也能简单了解到,就是说我们写的 Java 程序是和硬件无关的。比如说当我们要和硬件进行交互的时候,我们实际上就要程序是和虚拟机进行交互,然后再由虚拟机去和各个硬件进行交互这么一个过程。
虚拟机是 Java 平台无关的保障
那接下来我们再看一下虚拟机是 Java 平台无关的保障。对于这个可能大家刚开始学 Java 就知道 Java 有这么一个重要的特点,或者是特性叫做 Java 与平台无关。
那么它到底是如何实现的呢?大家看一下下面的这张图,其核心就是虚拟机。
大家看上面,首先 Java 原程序这个不用多,就是我们自己编写的 .java 文件,通过 javac 编译过后就形成 .class 文件。
这两个的过程其实是 Java 的开发过程。当我们有了这个 .class 文件过后,那么我们把它装载进虚拟机,真正的运行是在虚拟机里面去跑。比如说我们的 class 并不能运行,对吧。那真真是要到虚拟机里面去跑的。
而虚拟机就像我们的程序屏蔽了平台相关的一些东西。比如 Linux、Windows、MacOS。每个平台特性相关的一些东西,我们的应用程序这边是不管的,也就是说我们的应用程序就是与平台无关的。那这些平台相关的特性都有虚拟机去屏蔽掉了。
这个大家想一下,我们 Java 虚拟机在不同的这个平台上安装的时候。例如在我们的 Linux、Windows、MacOS 等操作系统上去安装的时候,我们安装的版本是不一样的。
其实实际上虚拟机的安装程序是跟平台有关的。那么跟平台无关的部分,是指我们开发的 Java 程序是跟平台无关的。也就说我们写程序的时候,我们不会针对Linux、Windows、MacOS 分别去写一套,而是写一套 Java 源程序统一针对虚拟机,至于虚拟机在哪个平台上我们不再关心了。所以说呢我们说 Java 是跟平台无关的。
那其实讲到这个地方可能有人会思考这样的问题。从这个角度来讲,我们的 Java 程序也并不是真的与平台无关,其实还是跟平台有关的。只不过我们这个平台只有一个就是 Java 虚拟机,也就说我们的 Java 程序是跟 Java 虚拟机这个平台相关的。
HellWorld 案例
最后,我们简单看一下一个 Java 程序的执行过程,它到底是如何运行起来的。
这里的 Java 程序是文本格式的。比如下面这段 HelloWorld.java,它遵循的就是 Java 语言规范。其中,我们调用了 System.out 等模块,也就是 JRE 里提供的类库。
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello World");
}
}
使用 JDK 的工具 javac 进行编译后,会产生 HelloWorld 的字节码。
我们一直在说 Java 字节码是沟通 JVM 与 Java 程序的桥梁,下面使用 javap 来稍微看一下字节码到底长什么样子。
0 getstatic #2 <java/lang/System.out>
3 ldc #3 <Hello World>
5 invokevirtual #4 <java/io/PrintStream.println>
8 return
Java 虚拟机采用基于栈的架构,其指令由操作码和操作数组成。这些字节码指令,就叫作 opcode。其中,getstatic、ldc、invokevirtual、return 等,就是 opcode,可以看到是比较容易理解的。
我们继续使用 hexdump 看一下字节码的二进制内容。与以上字节码对应的二进制,就是下面这几个数字(可以搜索一下)。
b2 00 02 12 03 b6 00 04 b1
我们可以看一下它们的对应关系。
0xb2 getstatic 获取静态字段的值
0x12 ldc 常量池中的常量值入栈
0xb6 invokevirtual 运行时方法绑定调用方法
0xb1 return void 函数返回
opcode 有一个字节的长度(0~255),意味着指令集的操作码个数不能操作 256 条。而紧跟在 opcode 后面的是被操作数。比如 b2 00 02,就代表了 getstatic #2 <java/lang/System.out>。
JVM 就是靠解析这些 opcode 和操作数来完成程序的执行的。当我们使用 Java 命令运行 .class 文件的时候,实际上就相当于启动了一个 JVM 进程。
然后 JVM 会翻译这些字节码,它有两种执行方式。常见的就是解释执行,将 opcode + 操作数翻译成机器代码;另外一种执行方式就是 JIT,也就是我们常说的即时编译,它会在一定条件下将字节码编译成机器码之后再执行。
这些 .class 文件会被加载、存放到 metaspace 中,等待被调用,这里会有一个类加载器的概念。
而 JVM 的程序运行,都是在栈上完成的,这和其他普通程序的执行是类似的,同样分为堆和栈。比如我们现在运行到了 main 方法,就会给它分配一个栈帧。当退出方法体时,会弹出相应的栈帧。你会发现,大多数字节码指令,就是不断的对栈帧进行操作。
而其他大块数据,是存放在堆上的。Java 在内存划分上会更为细致,关于这些概念,我们会在接下来的课时里进行详细介绍。
最后大家看下面的图,其中 JVM 部分,就是我们课程的要点。
小结
学习到这里,基本上对 Java 虚拟机有了一定的认识了,再来看一下本部分的课程小结。
认识JVM:重点是理解从 Java 源代码到运行的整个过程,初步了解 JVM 分成几个部分,建立起JVM的基本知识体系。