若是一个类加载器收到了加载某个类的请求,则该类加载器并不会去加载该类,而是把这个请求委派给父类加载器,每个层次的类加载器都是如此,所以全部的类加载请求最终都会传送到顶端的启动类加载器;只有当父类加载器在其搜索范围内没法找到所需的类,并将该结果反馈给子类加载器,子类加载器会尝试去本身加载。java
这样作的好处就是:Java类随着它的类加载器一块儿具有了一种带有优先级的层次关系。例如类java.lang.Object,它存放在rt.jar中,不管哪个类加载器要加载这个类,最终都是委派给处于模型最顶端的启动类加载器进行加载,所以Object类在程序的各类类加载器环境中都是同一个类。相反,若是没有使用双亲委派模型,由各个类加载器自行去加载的话,若是用户本身编写了一个称为java.lang.object的类,并放在程序的ClassPath中,那系统中将会出现多个不一样的Object类,Java类型体系中最基础的行为也就没法保证,应用程序也将会变得一片混乱。web
其次是考虑到安全因素。假设经过网络传递一个名为java.lang.Integer的类,经过双亲委托模式传递到启动类加载器,而启动类加载器在核心Java API发现这个名字的类,发现该类已被加载,并不会从新加载网络传递的过来的java.lang.Integer,而直接返回已加载过的Integer.class,这样即可以防止核心API库被随意篡改。sql
在JDBC 4.0以后实际上咱们不须要再调用Class.forName来加载驱动程序了,咱们只须要把驱动的jar包放到工程的类加载路径里,那么驱动就会被自动加载。数据库
这个自动加载采用的技术叫作SPI,数据库驱动厂商也都作了更新。能够看一下jar包里面的META-INF/services目录,里面有一个java.sql.Driver的文件,文件里面包含了驱动的全路径名。tomcat
使用上,咱们只须要经过下面一句就能够建立数据库的链接:安全
Connection con = DriverManager.getConnection(url , username , password ) ;
由于类加载器受到加载范围的限制,在某些状况下父类加载器没法加载到须要的文件,这时候就须要委托子类加载器去加载class文件。网络
JDBC的Driver接口定义在JDK中,其实现由各个数据库的服务商来提供,好比MySQL驱动包。DriverManager 类中要加载各个实现了Driver接口的类,而后进行管理,可是DriverManager位于 $JAVA_HOME中jre/lib/rt.jar 包,由BootStrap类加载器加载,而其Driver接口的实现类是位于服务商提供的 Jar 包,根据类加载机制,当被装载的类引用了另一个类的时候,虚拟机就会使用装载第一个类的类装载器装载被引用的类。也就是说BootStrap类加载器还要去加载jar包中的Driver接口的实现类。咱们知道,BootStrap类加载器默认只负责加载 $JAVA_HOME中jre/lib/rt.jar 里全部的class,因此须要由子类加载器去加载Driver实现,这就破坏了双亲委派模型。app
查看DriverManager类的源码,看到在使用DriverManager的时候会触发其静态代码块,调用 loadInitialDrivers() 方法,并调用ServiceLoader.load(Driver.class) 加载全部在META-INF/services/java.sql.Driver 文件里边的类到JVM内存,完成驱动的自动加载。webapp
static { loadInitialDrivers(); println("JDBC DriverManager initialized"); } private static void loadInitialDrivers() { AccessController.doPrivileged(new PrivilegedAction<Void>() { public Void run() { ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class); Iterator<Driver> driversIterator = loadedDrivers.iterator(); try{ while(driversIterator.hasNext()) { driversIterator.next(); } } catch(Throwable t) { // Do nothing } return null; } }); }
public static <S> ServiceLoader<S> load(Class<S> service) { ClassLoader cl = Thread.currentThread().getContextClassLoader(); return ServiceLoader.load(service, cl); }
这个子类加载器是经过 Thread.currentThread().getContextClassLoader() 获得的线程上下文加载器。jvm
那么这个上下文类加载器又是什么呢?
public Launcher() { ... try { this.loader = Launcher.AppClassLoader.getAppClassLoader(var1); } catch (IOException var9) { throw new InternalError("Could not create application class loader", var9); } Thread.currentThread().setContextClassLoader(this.loader); ... }
能够看到,在 sun.misc.Launcher 初始化的时候,会获取AppClassLoader,而后将其设置为上下文类加载器,因此线程上下文类加载器默认状况下就是系统加载器。
Tomcat类加载器:
Tomcat如何破坏双亲委派模型的呢?
每一个Tomcat的webappClassLoader加载本身的目录下的class文件,不会传递给父类加载器。
事实上,tomcat之因此造了一堆本身的classloader,大体是出于下面三类目的:
webapp
中的 class
和 lib
,须要相互隔离,不能出现一个应用中加载的类库会影响另外一个应用的状况,而对于许多应用,须要有共享的lib以便不浪费资源。jvm
同样的安全性问题。使用单独的 classloader
去装载 tomcat
自身的类库,以避免其余恶意或无心的破坏;tomcat
修改文件不用重启就自动从新装载类库而惊叹吧。