双亲委派机制简述
就是自己先不处理,让父加载器处理。这里不是用的继承,每个classLoad里面有一个parent的实例
父辈们都处理不了 自己动手处理。
public abstract class ClassLoader {
//每个类加载器都有个父加载器
private final ClassLoader parent;
public Class<?> loadClass(String name) {
//查找一下这个类是不是已经加载过了
Class<?> c = findLoadedClass(name);
//如果没有加载过
if( c == null ){
//先委托给父加载器去加载,注意这是个递归调用
if (parent != null) {
c = parent.loadClass(name);
}else {
// 如果父加载器为空,查找Bootstrap加载器是不是加载过了
c = findBootstrapClassOrNull(name);
}
}
// 如果父加载器没加载成功,调用自己的findClass去加载
if (c == null) {
c = findClass(name);
}
return c;
}
protected Class<?> findClass(String name){
//1. 根据传入的类名name,到在特定目录下去寻找类文件,把.class文件读入内存
...
//2. 调用defineClass将字节数组转成Class对象
return defineClass(buf, off, len);
}
// 将字节码数组解析成一个Class对象,用native方法实现
protected final Class<?> defineClass(byte[] b, int off, int len){
...
}
}
defineClass
它的职责是调用 native 方法把 Java 类的字节码解析成一个 Class 对象,所谓的 native 方法就是由 C 语言实现的方法,Java 通过 JNI 机制调用。
findClass
主要职责就是找到“.class”文件,可能来自文件系统或者网络,找到后把“.class”文件读到内存得到字节码数组,然后调用 defineClass 方法得到 Class 对象。
loadClass
它才是对外提供服务的接口,具体实现也比较清晰:首先检查这个类是不是已经被加载过了,如果加载过了直接返回,否则交给父加载器去加载。请你注意,这是一个递归调用,也就是说子加载器持有父加载器的引用,当一个类加载器需要加载一个 Java 类时,会先委托父加载器去加载,然后父加载器在自己的加载路径中搜索 Java 类,当父加载器在自己的加载范围内找不到时,才会交还给子加载器加载,这就是双亲委托机制。
处理范围
每个加载器处理的范围不同
自定义类加载器-就是看你的findClass和loadClass怎么写了
系统类加载器-它负责加载用户路径(ClassPath)上所指定的类库。我们自己编写的代码以及使用的第三方的jar包都是由它来加载的自定义加载器
extClassLoader- jre/lib/ext下的jar包
BootstrapClassLoader jvm的核心类库 rt 等
为什么要用双亲委派
主要处于安全性,防止核心类库的类被改写。你要是随便写一些基础String Object 那jvm就乱套了
tomcat是如何打破的
简单说就是让tomcat的webappClassLoader类加载器凌驾于系统类加载器之上
为什么打破?
主要在于如果classPath和web下面同名类,优先加载web应用路径下面那个
假如web应用目录下有个Spring2.0版本的库,系统class类路径下有个Spring 1.0版本的库,打破双亲委托的好处是web应用会加载web应用路径下那个Spring
Servlet规范建议,全路径类名与系统类同名的话,优先加载web应用自己定义的类
自定义类加载器重写了loadClass和findClass
tomcat重写了findClass流程
1.先使用webAppClassload去web应用目录下面查询类
2.没有找到,交给父加载器(系统类加载器)去查找
3.如果还没找到 throw new ClassNotFoundException
public Class<?> findClass(String name) throws ClassNotFoundException {
...
Class<?> clazz = null;
try {
//1. 先在Web应用目录下查找类
clazz = findClassInternal(name);
} catch (RuntimeException e) {
throw e;
}
if (clazz == null) {
try {
//2. 如果在本地目录没有找到,交给父加载器去查找
clazz = super.findClass(name);
} catch (RuntimeException e) {
throw e;
}
//3. 如果父类也没找到,抛出ClassNotFoundException
if (clazz == null) {
throw new ClassNotFoundException(name);
}
return clazz;
}
loadClass流程
1.查询本地缓存中有没有load过(有就返回,没有往下流转,下面都一样)
2.使用系统类加载器查看是否load过(看是否加载过,不是去加载)
3.使用ext类加载器加载(防止覆盖核心类库的类)
4.如果extClassLoad没有load到,使用tomcat的类加载器加载
5.都没有加载到,使用系统类加载器加载 Class.forName()默认使用的是系统类加载器
public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
Class<?> clazz = null;
//1. 先在本地cache查找该类是否已经加载过
clazz = findLoadedClass0(name);
if (clazz != null) {
if (resolve)
resolveClass(clazz);
return clazz;
}
//2. 从系统类加载器的cache中查找是否加载过
clazz = findLoadedClass(name);
if (clazz != null) {
if (resolve)
resolveClass(clazz);
return clazz;
}
// 3. 尝试用ExtClassLoader类加载器类加载,为什么?
ClassLoader javaseLoader = getJavaseClassLoader();
try {
clazz = javaseLoader.loadClass(name);
if (clazz != null) {
if (resolve)
resolveClass(clazz);
return clazz;
}
} catch (ClassNotFoundException e) {
// Ignore
}
// 4. 尝试在本地目录搜索class并加载
try {
clazz = findClass(name);
if (clazz != null) {
if (resolve)
resolveClass(clazz);
return clazz;
}
} catch (ClassNotFoundException e) {
// Ignore
}
// 5. 尝试用系统类加载器(也就是AppClassLoader)来加载
try {
clazz = Class.forName(name, false, parent);
if (clazz != null) {
if (resolve)
resolveClass(clazz);
return clazz;
}
} catch (ClassNotFoundException e) {
// Ignore
}
}
//6. 上述过程都加载失败,抛出异常
throw new ClassNotFoundException(name);
}