Java虚拟机详解(十一)------双亲委派模型

  在上一篇博客,咱们介绍了类加载过程,包括5个阶段,分别是“加载”,“验证”,“准备”,“解析”,“初始化”,以下图所示:html

  

 

  本篇博客,咱们来介绍Java虚拟机的双亲委派模型,在介绍以前,我先抛出一个问题:java

  咱们知道,在JDK源码中,有各类Java自带的类,好比java.lang.String,java.util.List等,那么咱们本身的项目中,可以写一个命名为java.lang.String.java 等JDK源码中存在的类,而且在项目中使用吗?算法

一、类加载器

  什么是类加载器?上篇博客咱们介绍类加载过程当中的第一个阶段——加载,做用是“经过一个类的全限定名来获取描述此类的二进制流”,那么这个加载过程就是由类加载器来完成的。数据库

  从Java虚拟机的角度出发,只存在两种不一样的类加载器,一种是启动类加载器(Bootstrap ClassLoader),这个类加载器使用 C++ 语言实现,是虚拟机自身的一部分;另外一种是全部其它的类加载器,这些类加载器都是由Java语言实现的。可是从Java开发人员的角度来看,类加载器能够细分为以下四种:安全

①、启动类加载器(Bootstrap ClassLoader)网络

  负责将存放在 <JAVA_HOME>/lib 目录中的,或者被-Xbootclasspath 参数所指定的路径中的,而且是虚拟机按照文件名识别的(仅按照文件名识别,如rt.jar,名字不符合的类库即便放在lib目录中也不会被加载)类库加载到虚拟机内存中。
  
启动类加载器没法被Java程序直接引用。
源码分析

  JDK 中的源码类大都是由启动类加载器加载,好比前面说的 java.lang.String,java.util.List等,须要注意的是,启动类 main Class 也是由启动类加载器加载。this

②、扩展类加载器(Extension ClassLoader)加密

   这个类加载器由 sun.misc.Launcher$ExtClassLoader 实现,负责加载<JAVA_HOME>/lib/ext 目录中的,或者被 java.ext.dirs 系统变量所指定的路径中的全部类库。spa

  开发者能够直接使用扩展类加载器。

③、应用程序类加载器(Application ClassLoader)

  由 sun.misc.Launcher$AppClassLoader 实现。因为这个类加载器是 ClassLoader.getSystemClassLoader() 方法的返回值,因此通常也称它为系统类加载器。

  它负责加载用户类路径ClassPath上所指定的类库,开发者能够直接使用这个类加载器。若是应用程序中没有自定义过本身的类加载器,通常状况下这个就是程序中默认的类加载器。

   一般项目中自定义的类,都会放在类路径下,由应用程序类加载器加载。

④、自定义类加载器(User ClassLoader)

   这是由用户本身定义的类加载器,通常状况下咱们不会自定义类加载器,但有些特殊状况,好比JDBC可以经过链接各类不一样的数据库就是自定义类加载器来实现的,具体用处会在后文详细介绍。

二、双亲委派模型

  回到文章开头提出的问题,若是有不法分子在你项目中构造了一个java.lang.String类,并在该类中植入了一些不良代码,但你本身浑然不知,觉得使用的String类仍是 rt.jar 包下的,那可能会给你系统形成不良的影响。

  聪明的Java虚拟机实现者也想到了这个问题,因而,他们引入了 双亲委派模型来解决这个问题。

  下面是双亲委派模型的加载流程机制:

  

  总结来讲:双亲委派机制就是若是一个类加载器收到了类加载请求,它首先不会本身尝试去加载这个类,而是把这个请求委派给父类加载器去完成,每个层次的类加载器都是如此,所以全部的加载请求最终都应该传送到顶层的启动类加载器中,只有父类加载器反馈到没法完成这个加载请求(它的搜索范围没有找到这个类),子加载器才会尝试本身去加载。

  其实,这里叫双亲委派可能有点不妥,由于按道理来说只有父加载器,这里的“双亲”是“parents”的直译,并不表示汉语中的父母双亲。另外,这里的父加载器也不是继承的关系。

 1 /**
 2  * Create by YSOcean
 3  */
 4 public class ClassLoadTest {
 5     public static void main(String[] args) {
 6         ClassLoader classLoader1 = ClassLoadTest.class.getClassLoader();
 7         ClassLoader classLoader2 = classLoader1.getParent();
 8         ClassLoader classLoader3 = classLoader2.getParent();
 9         System.out.println(classLoader1);
10         System.out.println(classLoader2);
11         System.out.println(classLoader3);
12     }
13 }

  输出为:

  

  那么知道了什么是双亲委派机制,双亲委派机制有什么好处呢?

  回到上面提出的问题,若是你自定义了一个 java.lang.String类,你会发现这个自定义的String.java能够正常编译,可是永远没法被加载运行。由于加载这个类的加载器,会一层一层的往上推,最终由启动类加载器来加载,而启动类加载的会是源码包下的String类,不是你自定义的String类。

三、双亲委派模型实现源码

  能够打开 java.lang.ClassLoader 类,其 loadClass方法以下:

 1     protected Class<?> loadClass(String name, boolean resolve)
 2         throws ClassNotFoundException
 3     {
 4         synchronized (getClassLoadingLock(name)) {
 5             // First, check if the class has already been loaded
 6             Class<?> c = findLoadedClass(name);
 7             if (c == null) {
 8                 long t0 = System.nanoTime();
 9                 try {
10                     if (parent != null) {
11                         c = parent.loadClass(name, false);
12                     } else {
13                         c = findBootstrapClassOrNull(name);
14                     }
15                 } catch (ClassNotFoundException e) {
16                     // ClassNotFoundException thrown if class not found
17                     // from the non-null parent class loader
18                 }
19 
20                 if (c == null) {
21                     // If still not found, then invoke findClass in order
22                     // to find the class.
23                     long t1 = System.nanoTime();
24                     c = findClass(name);
25 
26                     // this is the defining class loader; record the stats
27                     sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
28                     sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
29                     sun.misc.PerfCounter.getFindClasses().increment();
30                 }
31             }
32             if (resolve) {
33                 resolveClass(c);
34             }
35             return c;
36         }
37     }

  实现方式很简单,首先会检查该类是否已经被加载过了,若加载过了直接返回(默认resolve取false);若没有被加载,则调用父类加载器的 loadClass方法,若父类加载器为空则默认使用启动类加载器做为父加载器。若是父类加载失败,则在抛出 ClassNotFoundException 异常后,在调用本身的 findClass 方法进行加载。

四、自定义类加载器

  先说说咱们为何要自定义类加载器?

①、加密

  咱们知道Java字节码是能够进行反编译的,在某些安全性高的场景,是不容许这种状况发生的。那么咱们能够将编译后的代码用某种加密算法进行加密,加密后的文件就不能再用常规的类加载器去加载类了。而咱们本身能够自定义类加载器在加载的时候先解密,而后在加载。

②、动态建立

  好比颇有名的动态代理。

③、从非标准的来源加载代码

  咱们不用非要从class文件中获取定义此类的二进制流,还能够从数据库,从网络中,或者从zip包等。

  明白了为何要自定义类加载器,接下来咱们再来详述如何自定义类加载器。

  经过第 3 小节的  java.lang.ClassLoader 类的源码分析,类加载时根据双亲委派模型会先一层层找到父加载器,若是加载失败,则会调用当前加载器的 findClass() 方法来完成加载。所以咱们自定义类加载器,有两个步骤:

  一、继承 ClassLoader

  二、覆写 findClass() 方法

相关文章
相关标签/搜索