类加载机制
一个类从加载到使用,一般会经历下面这个过程
加载 -> 验证 -> 准备 -> 解析 -> 初始化 -> 使用 -> 卸载
JVM在什么情况下会加载一个类
JVM在执行过程中,一般在什么情况下会加载一个类呢?也就是说,啥时候会从 *.class字节码文件中加载这个到内存中,其实就是在你代码中使用到这个类的时候。
比如,代码中包含"main()"方法的主类一定会在JVM进程启动之后被加载到内存,开始执行你的"main()"方法中的代码,接着遇到你使用到的其他类,此时就会从对应的 .class字节码文件加载到对应的类到内存里来
验证、准备和解析过程
所谓的验证阶段,就是根据Java虚拟机规范,来校验加载进来的 .class文件中的内容,是否符合指定的规范,必须完全符合JVM规范,后续才能交给JVM来运行,否则,是无法执行这个字节码的
所谓的准备阶段,先看下一段代码
public class Test{
public static int a;
}
我们自己写的类,其实会有一些类变量,就如上面的Test类,其Test.class文件内容刚刚被加载到内存之后,会进行验证,确认这个字节码文件是规范的,接着会进行准备工作
就是给Test类分配一定的内存空间
然后给他里面的类变量(也就是static修饰的变量)分配内存空间,来一个默认的初始值
如上面的程序中,就会给a这个类变量分配内存空间,给一个0这个初始值
所谓的解析阶段,实际上是把符号引用替换成直接引用
核心阶段,初始化
在准备阶段,会为Test类给分配好内存空间,并给类变量a一个默认的初始值0,接下来,在初始化阶段,就会真正执行类初始化代码了
什么是类初始化代码呢,看下面代码
public class Test{
public static int a = Configuration.getInt("test.a");
public static Map<String, String> values;
static{
loadDataFromDB();
}
public static void loadDataFromDB(){
this.value = new HashMap<>();
}
}
对于a这个类变量,是通过 Configuration.getInt("test.a")来获取一个值,并赋值给a,那么在准备阶段会执行这个赋值逻辑么?
答案是否定的,在准备阶段,仅仅是给a这个变量开辟了一个内存空间,初始化为0而已
这段赋值代码在初始化阶段执行,在这个阶段,Configuration.getInt("test.a") 会完成一个配置项的读取,然后赋值给这个类变量a
再比如static静态代码块,也是在初始化阶段执行的,上面的代码,调用loadDataFromDB()方法从数据库获取数据,并且放到静态变量values中
以上就是类的初始化
那么什么时候会初始化一个类?有如下几种情况
包含"main()"方法的主类,必须是立马初始化的
new Test()来实例化类的对象,此时会触发类的加载到初始化的全过程,即把这个类准备好,然后再实例化一个对象出来
此外,如果初始化一个类的时候,发现他的父类还没初始化,那么必须先初始化他的父类
类加载器
java里面有哪些类加载器呢,一起看下
-
启动类加载器
Bootstrap ClassLoader,他主要负责加载机器上安装的Java目录下的核心类,即JAVA_HOME/lib目录,该目录下是Java最核心的一些类库,支撑Java系统运行,一旦JVM启动,就会依托启动类加载器,去加载JAVA_HOME/lib目录下的核心类库
-
扩展类加载器
Extension ClassLoader,JAVA_HOME/lib/ext目录,这里面也有一些类,就需要扩展类加载器来加载,支撑系统的运行,一旦JVM启动,就会依托扩展类加载器,去加载JAVA_HOME/lib/ext目录下的类
-
应用程序类加载器
Application ClassLoader,这个类加载器负责去加载"ClassPath"环境变量所指定的路径中的类,也就是加载自己编写的Java代码,到内存里
-
自定义加载器
可以自定义类加载器,根据需求加载类
双亲委派机制
JVM类加载器是有亲子层级结构的,就是说,启动器加载器是最上层的,扩展类加载器在第二层,第三层是应用程序类加载器,最后一层是自定义类加载器,如下图所示
基于上图的亲子层级结构,就有一个双亲委派的机制,什么是双亲委派机制呢,就是说
应用程序类加载器需要加载一个类,他首先会委派给自己的父类加载器去加载,最终传递到顶层的类加载器去加载,但是如果父类加载器在自己负责加载的范围内,没找到这个类,那么就会下推将加载圈给自己的子类下载器
用一个例子说明吧
比如现在JVM需要加载Test类,此时应用程序类加载器会问自己的爸爸,也就是扩展类加载器,你能加载这个类么?
扩展类加载器问自己的爸爸,启动类加载器,你能加载这个类么?
启动类加载器心想,我在Java安装目录下,没有找到这个类啊,自己找去吧
然后下推加载权利给扩展类加载器这个儿子,结果扩展类加载器找了下,也没找到这个类,就下推加载权利给应用程序类加载器
应用程序类加载器在自己负责的范围内,找到了这个类,就将这个类加载到内存里面去了
这就是双亲委派模型,先找父亲去加载,不行的话由儿子来加载
这样,可以避免多层级的加载器结构重复加载某些类