上一篇我们详细的讲解了java 虚拟机的内存模型(要想精通java,你必须得知道java的内存模型,不忽悠),并且知道了我们写的代码分别是怎么存在JVM的哪个内存区域中。今天我们来看另一个重要的问题,类加载机制的核心知识,这个知识也是面试经常问到的。
我们的java代码是怎么运行起来的?
我们在写好java代码之后,都是先进行打包,打成jar包或者war包,最后丢到服务器上启动JVM进程就能运行了。大概是下面这样的一个过程。
那么,现在我们就来看看这些类是怎么被加载到JVM中,它的加载机制又是什么样的
java类什么时候会被加载
我们写的java类,从被加载到使用,一般会经历下面几个步骤:
加载-->验证-->准备-->解析-->初始化-->使用-->卸载
我们首先就要知道,我们的类是什么时候被加载进JVM的?其实,我们有一定经验后就已经知道了,类在被使用的时候就会被加载进JVM中。
public class MyTest {
public static void main(String[] args) {
Study study = new Study();
study.studyJava();
}
}
//
public class Study {
public void studyJava() {
System.out.println("This is Java");
}
}
我们来看上面代码,当我们启动JVM运行到有main()方法的时候,这个时候MyTest.class这个类就会被加载进来,然后运行到有一个new Study() 的时候,发现需要实例化一个Study对象,就会去class文件找到study类加载进来,如下图
类连接过程
类的连接主要是指类验证、准备以及初始化的过程。这些我们暂时只用先了解下功能就行,一般在我们开发中不怎么会用到。
- 验证阶段:是指我们加载进来的class文件是否合法,有没有被篡改等
- 准备阶段:就是将我们加载来的类分配相应的内存空间,以及给变量指定初始值等,例如,private int type; 这种的
- 解析阶段:这个主要是将符号引用替换为直接引用的过程。
最为核心的阶段就是初始化,所以,我们来单独说一下初始化。
老规矩,我们先来看一段代码:
public class Study {
public static int strategyType = Configuration.getInt("config.strage.type");
}
上面我们说过在类的准备阶段,是分配内存空间以及给出初始化值的,然而我们现在的这个Configuration.getInt("config.strage.type") 在准备阶段会不会执行这段代码呢?其实是不会执行的,这里只会分配内存和给个初始值“0”,Configuration.getInt("config.strage.type") 是在初始化阶段进行执行的,然后再将结果赋值给strategyType的,还有就是我们经常写的static{}代码块也是在初始化阶段进行执行的。
什么时候会初始化一个类
现在我们已经弄明白了什么是初始化操作了,接下来我们来看看初始化的相关规则:
在new一个对象的时候,这个时候就会触发类的加载到初始化的全过程。
包含main()方法的类,需要立马初始化。
如果在初始化一个类时,发现他的父类没有进行初始化,那么就得先初始化父类。
类加载器以及双亲委派机制
通过上面的学习,我们已经很清楚了类的加载到初始化的整个过程,那么下面我们就需要去了解类加载器了,只有具备类加载器,上面那些过程才能得以实现。在我们java中主要有如下四种类加载器:
1,Bootstrap ClassLoader 启动类加载器
这个主要是用来加载我们安装在机器上JDK目录的核心类,即在我们java 目录的lib包里的各种jar包,我们启动JVM的时候,第一步就是加载这lib里面的类。
2,Extension ClassLoader 扩展类加载器
这个也是加载我们jdk默认的扩展类,在我们java目录的libext目录里面的各种jar包。
3,Application 应用程序类加载器
这个类加载器就是加载我们在classPath 环境变量指定的路径的类,也就是我们自己开发出来的各种类。
4,自定义类加载器
自定义就是根据自己的需要,设计一种自己的加载器去加载我们写的程序,和上面3种是互斥的。
双亲委派机制
上面我们将我们java里面的四大类加载器进行了梳理,并且知道了每个加载器各是加载什么内容的,那么最后我们还需要知道这些类加载器是怎么配合运行我们的系统的。
其实,JVM的类加载器是有亲子层级关系的,自上到下分别是启动类加载器、扩展类加载器、应用程序类加载器乃至最后一层是自定义类加载器。这就是一个双亲委派的机制。
怎么进行委派
就是指如果咱们的应用程序类加载器需要加载一个类的时候,它第一步就是先找它的父类加载器去加载,直到最顶层的类加载器。如果父类加载器在自己负责的范围内并未找到这个类的话,就会把加载的权利让给自己的子类去加载
我们还是来用一个例子来说明下吧,比如我们要加载Study这个类的时候:
- 应用程序类加载器会去找他的父亲扩展类加载器去帮它加载这个Study这个类。
- 扩展类加载器这个时候也会去请求他自己的父亲启动类加载器去帮忙加载Study这个类。
- 启动类加载器找了一圈发现这个Study并不在他的加载范围内,然后,就说不在我这儿,你自己去找。
- 扩展类就自己找,也找了一圈,仍然是没找到,就会下发他的儿子,你自己的类,你自己找去
- 应用程序类加载器这个时候就会自己找Study这个类,然后给加载进JVM中
下面我们来通过一张图来整体认识下类的加载过程以及双亲委派机制
总结,今天我们将我们常用的JVM类加载机制以及他的双亲委派模型进行了详细的讲解,大家只要认真学习了,相信,在我的工作开发中会更加的清晰明朗,同时,对于面试官的刁难也会有一套自己的应对方案。
在公众号“架构师修炼”可获得专属java架构视频资料,更多java、python、人工智能、小程序、大前端等可看菜单,无私奉献