JVM类加载机制以及双亲委派模型
一、java代码运行机制
首先对于我们开发者而言,编码文件都是java后缀的编码文件,那这个java后缀文件是怎么运行在我们计算机系统的呢?
通过编译成class文件然后交给jvm执行,如下列流程图:
graph LR
A[java后缀文件: user.java] --打包编译--> B[class后缀文件:user]
B--->jvm[jvm实例]
subgraph JVM
jvm--类加载器-->id3[类加载器]
id3-->id4[执行代码]
end
二、JVM是如何加载一个类的
一个类的加载一般回经历以下流程
- 验证:校验是否符合JVM字节码规范
- 准备:初始化内存
- 解析:符号引用替换为直接引用
- 初始化:执行初始化数据逻辑代码
引入一个案例来解析以上几种状态:
首先JVM什么时候回初始化类呢?那就是代码中使用到这个类的时候。看下面代码:
public class A {
public static void main(String args[]){
Replicamanager replicamanager = new Replicamanager();
}
public static class Replicamanager{
public static int flushInterval;
}
}
当jvm进程启动之后 ,先把Class A 加载到内存中去,然后从main方法执行入口,然后代码中明显用到了Replicamanager类,则也会加载Replicamanager类到内存中,则会触发类加载器。从“ReplicaManager.class”字节码文件中加载对应的类到内存里来使用,这样代码才能跑起来。
然后开始验证–准备–解析。
验证A.class 和B.class字节码是否符合规范,在准备A、B class文件,就是初始化A和B的初始引用地址比如B对象的flushInterval默认值等等,之后就是解析:符号引用替换为直接引用
如下图所示:
验证–准备–解析这三个阶段都是准备阶段,这个阶段初始化好了内存空间,也分配了默认值。
初始化(核心)
那么当准备阶段完毕之后就会初始化,接着上面的代码,此时flushInterval默认值也初始化好了就是0,那么初始化阶段,就会这是执行我们初始值代码:
public static class Replicamanager{
public static int flushInterval =
Configuration.getInt("replica.flush.interval");
}
现在对于flushInterval 准备用Configuration.getInt(“replica.flush.interval”);进行赋值。代码就是在这个时段执行的。
而类初始化还有一个规则:就是如果初始化一个类的时候,发现他的父类还没初始化,那么必须先初始化他的父类。
总结一下如下图所示
三、双亲委派机制
(1)启动类加载器 Bootstrap ClassLoader,他主要是负责加载我们在机器上安装的Java目录下的核心类的 相信大家都知道,如果你要在一个机器上运行自己写好的Java系统,无论是windows笔记本,还是linux服务器,是不是都得装一下 JDK? 那么在你的Java安装目录下,就有一个“lib”目录,大家可以自己去找找看,这里就有Java最核心的一些类库,支撑你的Java系统的 运行。 所以一旦你的JVM启动,那么首先就会依托启动类加载器,去加载你的Java安装目录下的“lib”目录中的核心类库。
(2)扩展类加载器 Extension ClassLoader,这个类加载器其实也是类似的,就是你的Java安装目录下,有一个“lib\ext”目录 这里面有一些类,就是需要使用这个类加载器来加载的,支撑你的系统的运行。 那么你的JVM一旦启动,是不是也得从Java安装目录下,加载这个“lib\ext”目录中的类?
(3)应用程序类加载器 Application ClassLoader,这类加载器就负责去加载“ClassPath”环境变量所指定的路径中的类 其实你大致就理解为去加载你写好的Java代码吧,这个类加载器就负责加载你写好的那些类到内存里。
(4)自定义类加载器 除了上面那几种之外,还可以自定义类加载器,去根据你自己的需求加载你的类。
(5)双亲委派机制 JVM的类加载器是有亲子层级结构的,就是说启动类加载器是最上层的,扩展类加载器在第二层,第三层是应用程序类加载器,最后一 层是自定义类加载器。