一、概述
上篇文章中概述了类的加载流程,那么类是由什么来加载的呢?毫无疑问就是类加载器,类加载器是Java中很重要的概念,它负责将字节码文件加载到Jvm虚拟机中。在Java中有三大类加载器:启动类加载器,扩展类加载器,应用程序类加载器。
二、启动类加载器
启动类加载器(Bootstrap ClassLoader)是由c/c++语言实现的,是JVM的一部分。
它用来加载Java的核心类库:rt.jar,resources.jar或sun.boot.class.path下的内容。
三、扩展类加载器
扩展类加载器(Extension ClassLoader)是由Java语言编写,由sun.misc.Launcher$ExtClassLoader实现。
派生于ClassLoader类。父类加载器为启动类加载器。
它用来加载Java_HOME/lib/ext中的类,以及 java.ext.dirs系统属性所指定的目录中的类库。
四、应用程序类加载器
应用程序类加载器(AppClassLoader)是由Java语言编写,由sun.misc.Launcher$AppClassLoader实现。
派生于ClassLoader类,父类加载器为扩展类加载器。
复杂加载环境变量中classpath或系统属性java.class.path指定路径下的类库。
应用程序类加载器是程序中默认的类加载器,一般Java应用的类都是由它完成加载。
public class Main {
public static void main(String[] args) {
ClassLoader loader = ClassLoader.getSystemClassLoader();
System.out.println(loader);
System.out.println(loader.getParent());
}
}
输出:
sun.misc.Launcher$AppClassLoader@73d16e93
sun.misc.Launcher$ExtClassLoader@15db9742
五、双亲委派机制
- 如果一个类加载器收到了类加载的请求,他并不会自己去加载,而是把这个请求委托给父类加载器。
- 如果父类加载器还存在父类加载器,则进一步向上委托,直到请求达到最顶层的启动类加载器。
- 如果父类加载器可以完成类的加载则返回,如果父类加载器无法完成加载任务,子加载器才会尝试自己去加载。这就是双亲委派机制。
举个例子:
假如我在项目目录下创建一个java.lang的包,然后再创建一个String类,代码如下:
package java.lang;
public class String {
static {
System.out.println("我是自定义的String");
}
}
然后我们来使用String:
class Test {
public static void main(String[] args) {
java.lang.String str = new java.lang.String();
System.out.println("Hello");
}
}
此时运行,这个String到底是哪个String呢?自定义的String还是Java核心库中的String呢?我们运行看下输出:
Hello
控制台只输出了Hello,而我们自定义的String中的静态代码块并没有执行,说明加载的是Java核心库的String。
可以说如果你自己创建了一个和Java库中同名的类,那么你的类永远得不到加载。
在这个例子中,如果Java使用Test的类加载器即应用程序加载器加载String,那么加载的就是我们自定义的String了,但是如果加载了自定义的String,万一你自定义的String有毒咋整?所以这就是为什么会有双亲委派机制。