类加载器定义
实现通过类的全限定名获取代表类的二进制流,并生成java.lang.Class对象的这一动作的代码被称为类加载器。
类加载器分类
通过上图我们可清晰得知类加载器主要分为三种,三种类加载器之间实际是组合关系并不是继承关系:
- 启动类加载器BootstrapClassLoader
由C语言编写的类加载器,也是最顶层的加载器,主要用来加载<JAVA_HOME>\jre\lib下的rt.jar、resourese.jar、charset.jar包里类,可以通过-Xbootclasspath设置加载指定路径中存放的类库。 - 扩展类加载器ExtClassLoader
是存在于launcher中的静态内部类,继承URLClassLoader,是用于加载<JAVA_HOME>\jre\lib\ext下的jar包里的类,可以通过java.ext.dirs系统变量设置加载指定路径中存放的类库。 - 应用程序加载器ApplicationClassLoader
是存在于launcher中的静态内部类,继承URLClassLoader,用于加载用户类路径classpath指定的所有类库。 - 自定义类加载器
非jdk自带的类加载器,是由我们自己通过继承抽象类ava.1ang.ClassLoader类的方式,可以实现自己的类加载器,以满足一些特殊的需求。
JDK1.2之前,我们总是通过去继承ClassLoader类并重写1oadClass()方法,从而实现自定义的类加载类,但在JDK1.2之后已不再建议用户去覆盖1oadclass()方法,而是建议把自定义的类加载逻辑写在findclass()方法中
在编写自定义类加载器时,如果没有太过于复杂的需求,可以直接继承URIClassLoader类,这样就可以避免自己去编写findclass()方法及其获取字节码流的方式,使自定义类加载器编写更加简洁。
双亲委派机制
双亲委派机制释义
当我们加载一个Object.class文件时,首先会在ApplicationClassLoader中检查是否已经加载过,如果有则无需再加载,如果没有则会拿到父加载器,然后调用父类的loadClass()方法,父类加载器也会检查类是否已加载过,如果没有加载再继续往上。直到到达最顶层类加载器BootstrapClassLoader,它上层是不存在父类加载器的,所以它会去尝试加载class类,如果无法加载则下层到子类加载器,子类加载器重复父类加载器的动作,直至ApplicationClassLoader类加载器,如果它也无法加载类,则会抛出ClassNotFoundException异常。
源码
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
// -----??-----
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
// 首先,检查是否已经被类加载器加载过
Class<?> c = findLoadedClass(name);
if (c == null) {
try {
// 存在父加载器,递归的交由父加载器
if (parent != null) {
c = parent.loadClass(name, false);
} else {
// 直到最上面的Bootstrap类加载器
c = findBootstrapClassOrNull(name);
}
} 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.
c = findClass(name);
}
}
return c;
}
双亲委派机制的好处
防止危险代码的注入,比如有人会尝试去替换系统级别的类如String.class,篡改它的代码实现,这种机制就可以很好的扼制这种危险发生,为什么呢?因为String.class已经被BootstrapClassLoader加载过了,那么它的子类加载器不会再去加载被篡改的String类了。