双亲委派模型
从虚拟机的角度来讲,只存在两种类加载器:
(1)启动类加载器:Bootstrap ClassLoader,由C++实现,不是ClassLoader子类,属于虚拟机自身的一部分
(2)所有其他类加载器:这些类加载器由Java语言实现,独立于JVM外部,全部继承自java.lang.ClassLoader
启动类加载器:Bootstrap ClassLoader
扩展类加载器:Extension ClassLoader
应用程序类加载器:App ClassLoader
自定义加载器:Custom ClassLoader
Bootstrap ClassLoader
作用:负责加载$JAVA_HOME中 jre/lib/rt.jar里所有的class,由C++实现,不是ClassLoader子类
Extension ClassLoader:负责加载java平台中扩展功能的一些jar包,包括$JAVA_HOME中jre/lib/*.jar或-Djava.ext.dirs指定目录下的jar包
App ClassLoader:负责加载classpath中指定的jar包及目录中的class
Custom ClassLoader:属于应用程序根据自身需要自定义的ClassLoader,如tomcat,jboss都会根据j2ee规范自行实现。
ClassLoader加载过程中会先检查类是否已被加载,检查过程中只要某个ClassLoader已加载就视为已加载此类,保证此类只被所有ClassLoader加载一次。
二.双亲委派模型
上面所示的类加载器之间的这种层次关系就称为类加载器的双亲委派模型。该模型要求除了顶层的启动类加载器外,其余的类加载器都应当有自己的父类加载器。子类加载器和父类加载器不是继承关系,而是通过组合关系来复用父加载器的代码。
双亲委派模型的工作过程为:如果一个类加载器收到了类加载的请求,它首先不会去尝试加载这个类,而是把这个请求为派给父类加载器去完成,每一个层次的加载器都是如此,因此所有的类加载请求都会传给顶层的启动类加载器,只有当父加载器反馈自己无法完成该加载请求(该加载器的搜索范围中没有找到对应的类)时,子加载器才会尝试自己去加载。
好处:使用这种模型来组织类加载器之间的关系的好处是Java类随着它的类加载器一起具备了一种带有优先级的层次关系。例如java.lang.Object类,无论哪个类加载器去加载该类,最终都是由启动类加载器进行加载,因此Object类在程序的各种类加载器环境中都是同一个类。否则的话,如果不使用该模型的话,如果用户自定义一个java.lang.Object类且存放在classpath中,那么系统中将会出现多个Object类,应用程序也会变得很混乱。如果我们自定义一个rt.jar中已有类的同名Java类,会发现JVM可以正常编译,但该类永远无法被加载运行。
三.protected synchronized Class loadClass(String name, boolean resolve)
throws ClassNotFoundException {
// 首先检查该name指定的class是否有被加载
Class c = findLoadedClass(name);
if (c == null) {
try {
if (parent != null) {
// 如果parent不为null,则调用parent的loadClass进行加载
c = parent.loadClass(name, false);
} else {
// parent为null,则调用BootstrapClassLoader进行加载
c = findBootstrapClass0(name);
}
} catch (ClassNotFoundException e) {
// 如果仍然无法加载成功,则调用自身的findClass进行加载
c = findClass(name);
}
}
if (resolve) {
resolveClass(c);
}
return c;
1.
loadClass()用于为派给父类加载类
JDK1.2之前,自定义类加载器都要覆盖loadClass方法去实现加载类的功能
JDK1.2引入双亲委派模型之后,loadClass方法用于委派父类加载器进行类加载,只有父类加载器无法完成类加载请求时才调用自己的findClass方法进行类加载,因此在JDK1.2之前的类加载的loadClass方法没有遵循双亲委派模型,因此在JDK1.2之后,自定义类加载器不推荐覆盖loadClass方法,而只需要覆盖findClass方法即可。
2.
双亲委派模式很好的解决了各个类加载器的基础类统一问题,越基础的类由越上层的类加载器进行加载,但这个基础类都有一个不足,当基础类想要调用回下层的用户代码时无法委派自类加载器进行加载,为了解决这个问题JDK引入了ThreadContext线程上下文,通过线程上下文的setContextClassLoader方法可以设置线程上下文类加载器
即:可由下到上委派,不可由上倒下委派。解决办法:线程上下文ThreadContext -> setContextClassLoader()
最后:
一道面试题
能不能自己写个类叫java.lang.System?
答案:通常不可以,但可以采取另类方法达到这个需求。
解释:为了不让我们写System类,类加载采用委托机制,这样可以保证爸爸们优先,爸爸们能找到的类,儿子就没有机会加载。而System类是Bootstrap加载器加载的,就算自己重写,也总是使用Java系统提供的System,自己写的System类根本没有机会得到加载。
但是,我们可以自己定义一个类加载器来达到这个目的,为了避免双亲委托机制,这个类加载器也必须是特殊的。由于系统自带的三个类加载器都加载特定目录下的类,如果我们自己的类加载器放在一个特殊的目录,那么系统的加载器就无法加载,也就是最终还是由我们自己的加载器加载。