Hello,大家好,我是 Steafan ,今天为大家介绍 JVM 是如何来处理 Java 字节码文件的。
一、博文内容概述
通过上一篇博文的介绍,我们已经对 JVM 中的垃圾处理过程有了一个简单基础性地了解,了解了 Java 对象是如何被回收的,以及常见的 JVM 垃圾收集处理算法,这对我们了解后续的 JVM 内容奠定了基础,其实垃圾收集过程还有很多细节之处没有给大家介绍,如果大家感兴趣,可以参考我的这篇博文-深入理解 Java GC 机制。
在了解了垃圾回收处理过程之后,接下来我们需要了解 Java 字节码文件在 JVM 中是如何被处理的,本篇博文内容如下图 1 所示:
图 1 Java 字节码文件处理基础内容
本篇博文会介绍一些关于 Java 字节码文件的基础概念、基本结构、JVM 基本解析过程,以及常见的一些 Java 主流的类加载器,本篇博文所介绍之内容不会太深入,只是从一个基础层面来了解 JVM 是如何来处理 Java 字节码文件的,如果大家感兴趣,可以在评论区留言,后续我会写一篇深入性的 JVM 处理 Java 字节码文件的博文。
二、Java 字节码文件处理-字节码文件基本结构
2.1 什么是 Java 字节码文件?
在了解 Java 字节码文件基本机构之前,让我们先来了解一下什么是 Java 字节码文件。Java 字节码文件相信我们都在大学中学 Java 基础内容的时候学过,下面让我们来回顾一下,所谓 Java 字节码文件,指的就是由我们写的 Java 类,经过 JVM 编译之后,形成的一种以 .class 文件格式结尾的文件,这就是 Java 字节码文件,为什么称为是“字节码”文件呢,因为我们写的 Java 代码在经 JVM 处理后,其就会形成 JVM 能识别的字节码。具体编译过程如下图 2 所示:
图 2 Java 代码编译为 Java 类文件的过程
由上图 2 可知,当我们的自行编写的 Java 代码运行的时候,无论我们是通过命令来执行 Java 代码,还是通过 IDE 来执行 Java 代码,其本质上都会调用 Java 的 Javac 命令来将我们自行编写的 Java 代码来编译成 Java 字节码文件。
那么,编译形成的 Java 字节码文件都有哪些元素构成呢,下面让我们来一探究竟。
2.2 Java 字节码文件基本组成内容
Java 字节码文件整体上由两部分内容组成,一部分是无符号数,另一部分则是表结构内容,其中,无符号数我们可以理解为我们的 Java 基本数据类型代码,其大小分别由 u1、u2、u4、u8 所代表的内存大表所决定,即 u1 代表无符号数占用 1 个字节的内存空间,u2 代表无符号数占用 2 个字节的内存空间,以此类推。
而表结构内容我们可以理解为我们的复合数据类型,即 Java 中的引用对象类型,在 class 字节码文件中,通常已 _info 结尾,其内容所占的空间是固定的,在一个类文件被加载完成后,表结构内容的空间就已经是固定的,且不能再次扩容或缩容。我们整个的 class 文件组成可以理解为就是由一张表结构所组成,只不过其中的无符号数可以看做是特殊的表结构中的内容罢了。class 文件的基本组成如下图 3 所示:
图 3 Java 类文件基本组成结构
2.3 Java 字节码文件特殊组成内容
在了解了 Java 字节码基础的组成内容之后,我们还需要了解 Java 字节码文件的特殊包含内容,这对我们后续理解 Java 类文件的解析过程有帮助。我们的 Java 代码在经过 JVM 编译,形成 Java 字节码文件之后,其构成 Java 字节码文件的前 12 为内容都是固定的,让我们来看一下这 12 位内容分别代表什么吧。
Java 字节码文件的前 8 为内容,表示 Java 中的魔法数字,也被称为魔数,前 8 位的值是固定的,即经典的 CAFEBABY ,其存在的意义在于,让 JVM 通过识别这个魔数的值,来判断是不是符合 JVM 解析的 Java 字节码文件,如果是 CAFEBABY ,则符合 JVM 要求,JVM 则继续解析 Java 字节码文件,如果不符合 JVM 要求,JVM 则不会继续解析该 Java 字节码文件内容。
紧接着 Java 字节码文件的第 9 、10 位内容,表示 Java 的此版本号,其为 16 进制内容,我们需要将该 16 进制内容换算成 10 进制内容,即可知道该 Java 字节码文件所使用的 JDK 的次版本是什么版本。
紧接着 Java 字节码文件的第 11、12 位内容,表示 Java 的主版本号,同样也是 16 进制内容,也需要我们将该 16 进制内容换算成 10 进制内容,即可知道该 Java 字节码文件所使用的 JDK 的主版本是什么版本。
三、Java 字节码文件处理-字节码文件解析过程
我们的 HotSpot 虚拟机在解析 Java 字节码文件的时候,整体会分为 7 个步骤,其具体解析步骤如下图 4 所示:
图 4 JVM 解析 Java 字节码文件过程
由上图可知,解析 Java 字节码文件共分为 7 个步骤,按顺序依次为加载、验证、准备、解析、初始化、使用、卸载。
当 Java 字节码文件被加载到 JVM 中之后,JVM 会首先判断这个 Java 字节码文件的魔数的值是不是 CAFEBABY ,如果不是 CAFEBABY ,则不会继续执行下面的流程,如果是 CAFEBABY ,则继续向下执行下面的流程。
经过加载阶段,执行到验证阶段的时候,JVM 会对 Java 字节码文件进行四部分内容的验证,分别是 Java 字节码结构验证、元数据内容验证、字节码格式验证、符号引用验证,其中,Java 字节码结构验证主要验证其 Java 字节码的构成方式是否符合 JVM 的规范要求,元数据验证主要验证位于 Java 字节码文件中的 Java 代码是否符合 Java 语法要求,字节码格式验证主要验证 Java 字节码文件中的字节码是否符合字节码的语法语义要求,以及由 Java 代码编译而来的字节码能否被 JVM 所执行,符号引用验证主要验证位于 Java 字节码文件中的 Java 代码的符号引用是否符合 Java 的语义要求,以及各个符号引用所修饰的变量范围是否正确。
在上述四个验证内容中,只要有一部分内容未验证通过,JVM 则会抛出错误,且不再继续执行这个 Java 字节码文件,只有这四部分内容均被验证通过之后,JVM 才会继续执行下述解析 Java 字节码文件的流程。
经过验证阶段,执行到准备阶段的时候,JVM 会将位于 Java 字节码文件中地 Java 变量的值进行赋值,其赋值的内容则是每一个 Java 变量所对应的零值,我们可以理解为是变量的初始值,待将所有变量的零值赋值完毕后,JVM 就会转入下一流程。
经过准备阶段,执行到解析阶段的时候,JVM 会将该 Java 字节码文件所对应的类的类型,以及类中变量和方法进行解析,以获取到 Java 字节码文件中基本的类型数据以及变量和方法数据,为后续的执行做数据准备。
经过解析阶段,执行到初始化阶段的时候,JVM 会将在准备阶段获取到的 Java 字节码文件中的基本类型数据以及变量和方法数据全部交给我们的应用本身去管控,我们的应用容器会再次对这些基础数据进行赋值,只不过此次所赋值的内容则是我们应用本身的数据,并且经过初始化阶段之后,JVM 不再继续执行我们的 Java 类文件,而是交给我们的应用本身去执行。
经过初始化阶段,执行到使用阶段的时候,此时,对于 Java 类文件的管控已经由 JVM 转移到应用容器,所以,这个 Java 类文件什么时候被使用,则是由应用容器去调度,而应用容器进行调度所依据的条件就是我们实际的业务逻辑了。
一个 Java 类文件在被使用完毕之后,就到了最后的卸载阶段,在卸载阶段,应用容器会首先释放位于应用容器中 Java 类文件所占用的内存空间,之后,通知 JVM 容器来将该 Java 类文件进行卸载,同样也是清空并释放该 Java 类文件所占用的 JVM 的内存空间,如此往复重复这一过程。
在上述的验证、准备、解析三个阶段,有时也被称为 JVM 的连接阶段,这点朋友们了解即可。
至此,解析 Java 类文件的 7 个步骤就全部执行完毕了。
四、Java 字节码文件处理-主流类加载器介绍
众所周知,解析任何一个、任何一种 Java 代码都需要类加载的介入才能进行解析,那么 JVM 中都使用了哪些类加载器呢,下面让我们一一来看。
截止到 JDK8 ,JVM 中所使用的类加载器如下图 5 所示:
图 5 JVM 中所使用的类加载器
由上图 5 可知,JVM 中现在主要使用的是 4 类类加载器,分别是启动类加载器、拓展类加载器、应用类加载器、自定义类加载器。
启动类加载器,即 Bootstrap ClassLoader ,用于加载 JDK 安装目录下 /jre/lib 路径下的 jar 包资源到 JVM 容器中。
拓展类加载器,即 Extension ClassLoader ,用于加载 JDK 安装目录下 /jre/lib/ext 路径下的 JDK 自带的拓展 jar 包资源到 JVM 容器中。
应用类加载器,即 Application ClassLoader ,用于加载 Java 应用所需的 jar 包资源到 JVM 容器中。
自定义类加载器,即 Custom ClassLoader ,用于加载开发者自定义的,不适用上述类加载器进行加载的 Java 类文件。
出于 Java 语言灵活性角度考虑,我们可以根据我们的实际业务需要,来灵活的定义属于业务本身的 Java 类加载器,然后通过一定的措施,在 JVM 中来使用我们的自定义类加载器,来满足我们的实际需要,正式由于 Java 的这一特性,我们才能实现跨平台、跨语言的程序调用,Java 才得以保持持续流行的态势。
至此,本篇博文的所有内容就介绍完毕了,最后,感谢各位朋友和粉丝的支持,谢谢你们!!!



