本文只是对类加载器的加载原理做一简述,如有不对,请指正。
1. 加载之前
- 原理图
- 原理简述
2.1 自己编写一个Myclass.java文件。
2.2 在运行该文件时,首先通过javac Myclass.java 将该文件编译成Myclass.class文件(默认会在当前目录下)。
2.3 通过类加载器将Myclass.class文件加载到JVM中的运行时数据区,再通过执行引擎将加载到运行时数据区的字节码文件
2. 加载时
- 原理图
- 加载源码
首先判断当前类加载器是否有父类,如果有就调用父类加载器进行加载,否则就掉用Bootstrap类加载器进行加载,当Bootstrap类加载器进行加载失败时,抛出类为发现异常,就会调用当前类加载器进行加载。
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false); // 委托父类加载
} else {
c = findBootstrapClassOrNull(name); // 委托Bootstrap加载
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name); // 当前类加载器加载
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
- 原理简述
当类加载器加载Myclass.class时,会自底向上检查该Myclass.class是否已经被加载过,假如在Extension class loader处发现,该Myclass.class已经被加载过,则Myclass.class将不会被再次加载,而是会使用已经加载过的Myclass.class。加入检查到Bootstrap class loader 处也没有发现该Myclass.class被加载过,此时则会自顶向下进行尝试加载,首先Bootstrap class loader尝试在java_home/lib目录下找Myclass.class是否存在,存在就会被加载并完成Myclass.class的加载行为,否则继续向下尝试加载,当找到Custom class loader都没有找到Myclass.class时,则会抛出ClassNoFoundException异常。
3. 类加载之后
- 原理图
- 原理简述
2.1 加载完成后,首先检查加载的类文件是否符合java规范和虚拟机规范
2.2 分配所需内存,确定类属性、方法等所需的数据结构
2.3 将常量池中的符号引用改变为直接引用
2.4 初始化类的局部变量,为静态域赋值,执行静态初始化块。 - 限制
3.1 下层的加载器能够看到上层加载器中的类,反之则不行
3.2 类加载器可以加载一个类,但是不能卸载一个类。但是类加载器可以被删除或者被创建
4. 双亲委派模型是什么?
如果一个类加载器收到一个加载某个类的请求,先去查找该类是否被已经加载过,如果被加载过就直接返回,否则该类加载器不会去加载该类,而是把该请求层层委托给父类加载器,每一层都是如此,直至递归到最顶层的类加载器(启动类加载器),只有当父类加载器在其搜索范围内无法找到所需的类时,子类加载器才会尝试去加载类。
5. JDBC为什么要破坏双亲委派模型?
因为类加载器受到加载范围受限,在某些情况下父类加载器无法加载到需要的文件,这时候就需要委托子类加载器去加载class文件。
JDBC的**Driver接口定义在JDK中,其实现是由各个数据库的服务商来提供,**比如MySql驱动包。DriverManager类中要加载各个实现了Driver接口的类,然后进行管理,但是DriverManager位于$JAVA_HOME中的jre/lib/rt.jar包,由于BootStrap类加载器来进行加载,而其Driver接口的实现类是位于服务商提供的jar包中的,**根据类加载器限制,当被装载的类引用另外一个类的时候,虚拟机就会使用装载第一个类的类加载器来加载被引用的类。**也就是说BootStrap类加载器还要去加载jar包中的Driver接口的实现类。我们知道BootStrap类加载器默认只负责加载$JAVA_HOME中jre/lib/tr.jar中的class文件,所以Driver接口的实现类根本不在这个路径下,因此需要有子类加载器去加载Driver接口的具体实现类。这样就破坏了双亲委派模型。
那么父类加载器怎么通过子类加载器去加载类文件呢,线程上下文类加载器可以通过Thread.setContThread.setContextClassLoaser()方法设置,如果不设置默认会从父类继承,一般默认使用的是应用程序类加载器。通过
Thread.currentThread().getContextClassLoader()来获取子类类加载器
6. Tomcat为什么要破坏双亲委派模型?
每个Tomcat的webappClassLoader加载自己的目录下的class文件,不会传递给父类加载器。
事实上,tomcat之所以造了一堆自己的classloader,大概有三个目的:
- 热部署
- 与jvm一样的安全性问题,使用单独的classloader去装载tomcat自身的类库,以免其他恶意破坏。
https://blog.csdn.net/qq_38182963/article/details/78660779