0%

类加载器分类

类加载器分类

有三种默认使用的类加载器,分别是Bootstrap ClassLoader、Extension ClassLoader和System ClassLoader(也称为Application ClassLoader),除此之外还可以自定义类加载器,每种类加载器已经确定从哪个位置加载类文件。

Bootstrap ClassLoader引导类加载器

Bootstrap ClassLoader是由C/C++来编写的,负责加载JDK自带的lib目录下如rt.jar包中(名字不符合的类库即使放在lib目录下也不会被加载)的类文件以及sun.boot.class.path路径下的内容,是所有的类加载器的父加载器,并没有继承ClassLoader类

Extension ClassLoader扩展类加载器

Extension ClassLoader是由java语言编写的,sun.misc.Launcher.ExtClassLoader实现,继承了ClassLoader类,负责加载Java扩展类库,从lib/ext目录下或者java.ext.dirs系统属性指定的目录下加载类,父类加载器是Bootstrap ClassLoader

Application ClassLoader系统类加载器

Application ClassLoader是由java语言编写的,sun.misc.Launcher.AppClassLoader实现,继承了ClassLoader类,负责从classpath环境变量中加载类文件,classpath环境变量通常由”-classpath”或”-cp”命令行选项来定义,或是由JAR中Mainifest文件的classpath属性指定,父加载器是Extension ClassLoader,该加载器是程序默认的加载器,一般情况下,java中的类都是由该加载器加载的

用户自定义类加载器

自定义类加载器可以实现应用隔离,如Tomcat、spring中都有自定义的加载器,通过自定义加载器隔离不同的组件模块,自定义类加载器需要继承ClassLoader

主要方法

findClass
1
protected Class<?> findClass(String name)

该方法会在检查完父类加载器之后被loadClass()方法调用,当loadClass()方法中父加载器加载失败后,则会调用自己的findClass()方法来完成类加载,保证了自定义的类加载器也符合双亲委派机制,通常重写该方法来完成自定义的类加载规则,取得要加载的类的字节码,然后调用defineClass方法生成类的Class对象

defineClass
1
protected final Class<?> defineClass(String name, byte[] b, int off, int len)

该方法是用来将byte字节流解析成JVM能够识别的Class对象,通过该方法不仅能够通过class文件实例化class对象,也可以通过网络接收一个类的字节码,转为byte字节流创建对应的class对象,一般与findClass搭配使用

loadClass
1
public Class<?> loadClass(String name)

该方法是实现双亲委派机制逻辑的地方,一般不建议自定义类加载器重写该方法,而是重写findClass方法,但是如果需要编写的自定义类加载器不需要遵循双亲委派机制的话,则需要重写该方法

resolveClass
1
protected final void resolveClass(Class<?> c)

链接指定一个java类,使用该方法可以使用类的class对象创建完成的同时也被解析,主要是对字节码进行验证,为类变量分配内存并设置初始值同时将字节码文件中的符号引用转为直接引用

类加载器通过组合的方式来进行维系父子关系

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public abstract class ClassLoader {

// The parent class loader for delegation
// Note: VM hardcoded the offset of this field, thus all new fields
// must be added *after* it.
private final ClassLoader parent;

private ClassLoader(Void unused, ClassLoader parent) {
this.parent = parent;
if (ParallelLoaders.isRegistered(this.getClass())) {
parallelLockMap = new ConcurrentHashMap<>();
package2certs = new ConcurrentHashMap<>();
assertionLock = new Object();
} else {
// no finer-grained lock; lock on the classloader instance
parallelLockMap = null;
package2certs = new Hashtable<>();
assertionLock = this;
}
}


protected ClassLoader(ClassLoader parent) {
this(checkCreateClassLoader(), parent);
}


protected ClassLoader() {
this(checkCreateClassLoader(), getSystemClassLoader());
}

}

数组类型的类加载器与数组元素的类加载器一致

双亲委派机制

双亲委派

双亲委派的原理

根据双亲委派机制,在加载类文件时

  • 先检测是否已经加载过该类,如果加载过,则直接返回
  • 判断当前加载器的父加载器是否为空,如果不为空,则将加载请求委托给它的父加载器,父加载器会检测自己是否已经加载过该类,如果已经加载则加载过程结束;如果未加载则请求继续向上传递,直到Bootstrap ClassLoader。如果在请求向上委托的过程中,始终未检测到该类已加载,则从Bootstrap ClassLoader开始尝试从其对应路径中加载该类文件,如果加载失败,则向下传递,直到发起请求的子加载器位置为止
  • 如果当前加载器的父类加载器为空,则调用findBootstrapClassOrNull(name),让引导类加载器进行加载

双亲委派的作用

  • 避免类的重复加载,保证一致性
  • 保护程序的安全,防止JAVA核心API定义的类型不会被随意替换
  • 子加载器可以使用父加载器已加载的类,而父加载器无法使用子加载器已加载的类
  • 父加载器已加载过的类无法被子加载器再次加载,保证JVM的安全性和稳定性

双亲委派的弊端

检查类是否加载的委托过程是单向的,会导致顶层的ClassLoader无法访问底层的ClassLoader所加载的类

注意:在Servlet规范中对于Web应用的类加载器的实现方式的推荐做法是使用当前类加载器优先的策略来代替默认的双亲委派,目的是为了解决web应用中的第三方库和容器本身使用的第三方库冲突的问题。如果采用双亲优先的话,那么容器中提供的第三方库会被优先加载,web应用中使用了同样的库,但是版本不同,会导致web应用出现错误

以Tomcat为例

Bootstrap 引导类加载器:加载JVM启动所需的类以及扩展类(jre/lib/ext下)

System 系统类加载器:加载Tomcat启动的类,比如bootstrap.jar,通常在catalina.bat或者catalina.sh中指定,位于CATALINA_HOME/bin下

Common 通用类加载器:加载Tomcat使用以及应用通用的一些类,位于CATALINA_HOME/lib下。如servlet-api.jar

webapp 应用类加载器:每个应用在部署后,都会创建一个唯一的类加载器,该类加载器位于WEB-INF/lib下的jar文件中的class和WEB-INF/classes下的class文件

欢迎关注我的其它发布渠道