目录
一、类加载器是什么
Java是运行在Java的虚拟机(JVM)中的,但是它是怎么就运行在JVM中了呢?我们在IDE中编写的Java源代码被编译器编译成.class的字节码文件。然后由我们得ClassLoader(类加载器)负责将这些class文件加载到JVM中去执行。
JVM中提供了三层的ClassLoader:
每种类加载器负责加载的类都不同:
-
Bootstrap classLoader(引导类加载器):主要负责加载核心的类库(java.lang.*等),构造ExtClassLoader和APPClassLoader。
-
ExtClassLoader(扩展类加载器):主要负责加载Java的扩展库,如jre/lib/ext目录下的一些扩展的jar。
-
AppClassLoader(应用类加载器):主要负责加载应用程序的主函数类或者自己写的类
通过一个小小的例子来看看,主函数类的是否真的通过AppClassLoader加载
public class HelloWorld{
public static void main(String[] args){
HelloWorld h = new HelloWorld();
Class<?> c = h.getClass(); //获取HelloWorld的类文件
/*下面语句输出://jdk.internal.loader.ClassLoaders$AppClassLoader@2f0e140b,看到AppClassLoader了没有~*/
System.out.println(c.getClassLoader());
}
}
除了JVM提供的ClassLoader(类加载器),用户还能自己创建加载器(例如叫CustomClassLoader)。
注意:一个类加载器只要加载过一个类,类加载器在成功加载某个类之后,会把得到的 java.lang.Class
类的实例缓存起来。下次再请求加载该类的时候,类加载器会直接使用缓存的类的实例,而不会尝试再次加载。
二、类的加载过程(双亲委派机制)
当某个类加载器需要加载某个.class文件时,它首先把这个任务委托给他的上级类加载器,递归这个操作,如果上级的类加载器没有加载,自己才会去加载这个类。
例如现在要加载一个 Car.class 类,具体过程如下图:
先检查在CustomClassLoader有没有加载过,如果有,就不需要再加载了,如果没有就往上到ExtClassLoader(扩展类加载器)处检测,同样的流程,到BootStrapClassLoader如果还是没发现加载过这个Car.class,就从BootStrapClassLoader看看是否可以找到这个类并加载,若没有就到ExtClassLoader处找,如果到CustomerClassLoader(这表示用户自己写的类加载器,平时不一定存在)都没找到Car.class可以加载,就抛出classNotFound异常。
为什么要设计这种模式?
这种设计有个好处是,如果有人想替换系统级别的类:String.java。篡改它的实现,但是在这种机制下这些系统的类已经被Bootstrap classLoader加载过了,所以并不会再去加载,从一定程度上防止了危险代码的植入。
在JVM中,辨别两个类是否属于是同一个类并非仅仅通过类的全名,而是通过类的全名+加载这个类的类加载器 来确定的。如果不是这种双亲委派的模式,所有类都可以通过不同的加载器加载,那么就算两个类的字节码完全相同,也会被JVM标识成不同的两个类,那么这两个类就不能进行互相赋值等操作,会抛出运行时异常 ClassCastException
。