类加载阶段分为加载、链接、初始化三个阶段,而加载阶段须要经过类的全限定名来获取定义了此类的二进制字节流。Java特地把这一步抽出来用类加载器来实现。把这一步骤抽离出来使得应用程序能够按需自定义类加载器。而且得益于类加载器,OSGI、热部署等领域才得以在JAVA中获得应用。java
在Java中任意一个类都是由这个类自己和加载这个类的类加载器来肯定这个类在JVM中的惟一性。也就是你用你A类加载器加载的com.aa.ClassA
和你A类加载器加载的com.aa.ClassA
它们是不一样的,也就是用instanceof
这种对比都是不一样的。因此即便都来自于同一个class文件可是由不一样类加载器加载的那就是两个独立的类。mysql
类加载器除了能用来加载类,还能用来做为类的层次划分。Java自身提供了3种类加载器面试
一、启动类加载器(Bootstrap ClassLoader),它是属于虚拟机自身的一部分,用C++实现的,主要负责加载<JAVA_HOME>\lib
目录中或被-Xbootclasspath指定的路径中的而且文件名是被虚拟机识别的文件。它等因而全部类加载器的爸爸。sql
二、扩展类加载器(Extension ClassLoader),它是Java实现的,独立于虚拟机,主要负责加载<JAVA_HOME>\lib\ext
目录中或被java.ext.dirs系统变量所指定的路径的类库。数据库
三、应用程序类加载器(Application ClassLoader),它是Java实现的,独立于虚拟机。主要负责加载用户类路径(classPath)上的类库,若是咱们没有实现自定义的类加载器那这玩意就是咱们程序中的默认加载器。bash
知道上面这几个概念就能来看看双亲委派模型了。oracle
双亲委派的意思是若是一个类加载器须要加载类,那么首先它会把这个类请求委派给父类加载器去完成,每一层都是如此。一直递归到顶层,当父加载器没法完成这个请求时,子类才会尝试去加载。这里的双亲其实就指的是父类,没有mother。父类也不是咱们平日所说的那种继承关系,只是调用逻辑是这样。分布式
{
// First, check if the class has already been loaded 先判断class是否已经被加载过了
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false); //找他爸爸去加载
} else {
c = findBootstrapClassOrNull(name); //没爸爸说明是顶层了就用Bootstrap ClassLoader去加载
}
} 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.
long t1 = System.nanoTime();
c = findClass(name); //最后若是没找到,那就本身找
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
复制代码
双亲委派模型不是一种强制性约束,也就是你不这么作也不会报错怎样的,它是一种JAVA设计者推荐使用类加载器的方式。ide
双亲委派有啥好处呢?它使得类有了层次的划分。就拿java.lang.Object
来讲,你加载它通过一层层委托最终是由Bootstrap ClassLoader
来加载的,也就是最终都是由Bootstrap ClassLoader
去找<JAVA_HOME>\lib
中rt.jar里面的java.lang.Object
加载到JVM中。性能
这样若是有不法分子本身造了个java.lang.Object
,里面嵌了很差的代码,若是咱们是按照双亲委派模型来实现的话,最终加载到JVM中的只会是咱们rt.jar里面的东西,也就是这些核心的基础类代码获得了保护。由于这个机制使得系统中只会出现一个java.lang.Object
。不会乱套了。你想一想若是咱们JVM里面有两个Object,那岂不是天下大乱了。
所以既然推荐使用这种模型固然是有道理了。
可是人生不如意事十之八九,有些状况不得不违反这个约束,例如JDBC。
你先得知道SPI(Service Provider Interface),这玩意和API不同,它是面向拓展的,也就是我定义了这个SPI,具体如何实现由扩展者实现。我就是定了个规矩。
JDBC就是如此,在rt.jar里面定义了这个SPI,那mysql有mysql的jdbc实现,oracle有oracle的jdbc实现,反正我java无论你内部如何实现的,反正大家都得统一按我这个来,这样咱们java开发者才能容易的调用数据库操做。因此由于这样那就不得不违反这个约束啊,Bootstrap ClassLoader
就得委托子类来加载数据库厂商们提供的具体实现。由于它的手只能摸到<JAVA_HOME>\lib
中,其余的它无能为力。这就违反了自下而上的委托机制了。
Java就搞了个线程上下文类加载器,经过setContextClassLoader()
默认状况就是应用程序类加载器而后Thread.current.currentThread().getContextClassLoader()
得到类加载器来加载。
若有错误欢迎指正!
我的公众号:yes的练级攻略
有相关面试进阶(分布式、性能调优、经典书籍pdf)资料等待领取