前面我们分析了,JVM 的整体的一个运行原理
我们首先从“.java”代码文件,编译成 “.class” 字节码文件
然后,类加载器把“.class”字节码文件中的类加载到JVM中,
接着就是JVM 运行我们写好的那些类中代码。
上面就是我们之前的分析类,下面我么着重分析其中的“类加载”过程,看看JVM 的类加载机制到底是怎么样的?
1、JVM 什么情况下会加载一个类
一个类从加载到使用,一般会经历下面的几个过程:
加载 --> 验证 --> 准备 --> 解析 --> 初始化 --> 使用 --> 卸载
所以,我们得知道,JVM 在执行我们写好的代码过程中,一般什么情况下才会加载一个类?
也就是,啥时候会“.class”字节码文件中这个类到 JVM,其实就是在你使用到这个类的时候。
举个例子:
public class Kafka { public static void main() { ReplicaManager replicaManager = new ReplicaManager(); } }
上面有个类 Kafka.class ,里面有一个 main() 方法作为 主入口。
那么 一旦你的 JVM 进程启动后,它一定会先把你的这个类(Kafka.class)加载到内存,然后从 “main()”方法开始执行。
在 mian() 方法里面,我们明显需要使用 ReplicaManager 这个类去实例化一个对象,此时就必须把 “ReplicaManager.class” 加载到内存里面。
简单概括一下就是:
首先代码中包含“main()”方法的主类一定会在JVM 进程启动的时候被加载到内存,开始执行你的“main()” 方法中方的代码,接着遇到使用类别的类,此时就会从对用的“.class” 中加载对应的类到内存中。
2、 从使用的角度看看,验证、准备和初始化的过程
(1)验证阶段
简单的说,这一步就是根据 Java 虚拟机规范,来校验你加载进来的 “.class” 文件中的内容是否符合指定的规范。
(2)准备阶段
这个阶段的主要工作就是,给我写好的类中的一些变量分配空间。
public class ReplicaManager{ public static int flushInterval; }
如上面的代码,在准备阶段其实就给这个 “ReplicaManager” 分配一定的内存空间,
然后,给他里面的类变量(也就是static修饰的变量)分配内存空间,来一个默认的初始值。
比如上面的 flushInterval 给一个 “0”的默认值。
(3)解析阶段
这个阶段干的事儿,实际上是把符号引用替换为直接引用的过程,其实这个部分的内容很复杂,涉及到JVM的底层,从实用角度而言,对很多同学在工作中实践JVM技术其实也用不到,所以这里大家 就暂时知道有这么一个阶段就可以了。
三个阶段小结:
其实,这三个阶段里,最核心的就是“准备阶段”,因为这个阶段是给加载进来的类分配好内存空间,类变量也分配好内存空间,并且给默认值。
(4)核心阶段:初始化
之前说类,在准备阶段,会给类分配内存空间,
那么什么是类的初始化代码呢?
public class ReplicaManager{ public static int flushInterval = Configuration.getInt("replica.flush.interval"); static { loadReplicalFromDish(); } }
如上面这段代码,对于 “flushInterval” ,我们打算通过“Configuration.getInt("replica.flush.interval")”来获取值并赋值给他。这段赋值的代码什么时候会执行呢?
就是在“初始化”阶段来执行。另外,static 静态代码块也会在这一个阶段来执行。
什么时候会初始化一个类?
一般来说,有以下时机:
1、通过 new XX() 来实例化类的对象,会触发类的加载到初始化的全过程;
2、包含“main()” 方法的主类,必须是立马初始化的
3、如果初始化一个类的时候,必须先初始化他的父类。
(5)类加载器
Java 的类加载,简单来说有下面几种
1、启动类加载器
Bootstrap ClassLoader,他主要是负责加载我们在机器上安装的Java目录下的核心类的,JVM启动,那么首先就会依托启动类加载器,去加载你的Java安装目录下的“lib”目录中的核心类库。
2、扩展类加载器
Extension ClassLoader,这个类加载器其实也是类似的,就是你的Java安装目录下,有一个“lib\ext”目录
3、应用程序类加载器
Application ClassLoader,这类加载器就负责去加载“ClassPath”环境变量所指定的路径中的类 其实你大致就理解为去加载你写好的Java代码吧,这个类加载器就负责加载你写好的那些类到内存里。
4、自定义类加载器
除了上面的几种,还可以自定义类加载器,去根据你自己的需求加载你的类
(6)双亲委托机制
JVM 的类加载器,是分层级的,启动类加载器是最上层的,扩展类加载器在第二层,第三层是 应用程序类加载器,最下面的是自定义类加载器
然后基于这个亲子层级架构,就有一个 双亲委托机制
所谓的双亲委托模型:就是 先找父亲去加载,不行的话再由儿子来加载。
这样的话就避免来多层级的加载器结构重复加载某些类。