讲个故事:

以前,爱捣鼓的小明突然灵机一动,写出了下面的代码

package java.lang;   public class String {     //...复制真正String的其他方法          public boolean equals(Object anObject) {         sendEmail(xxx);         return equalsReal(anObject);     }          //... } 

这样,只要引用java.lang.String的人,小明能随时收到他的系统的相关信息,这简直是个天才的注意。然而实施的时候却发现,JVM并没有加载这个类。

这是为什么呢?

小明能想到的事情,JVM设计者也肯定能想到。

双亲委派模型

上述故事纯属瞎编,不过,这确实是以前JVM存在的一个问题,这几天看Tomcat源代码的时候,发现频繁出现ClassLoader为什么要用这个东西呢?

想要解答这个问题,得先了解一个定义:双亲委派模型。

这个词第一次看见是在《深入理解JVM》中,目的也是为了解决上面所提出来的问题。

在JVM中,存在三种类型的类加载器:

  • 启动类(Bootstrap)加载器: 用于加载本地(Navicat)代码类的加载器,它负责装入%JAVA_HOME%/lib下面的类。由于引导类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用,所以不允许直接通过引用进行操作。
  • 标准扩展(Extension)类加载器: 由ExtClassLoader实现,负责加载%JAVA_HOME/lib/ext%或者系统变量java.ext.dir(可使用System.out.println("java.ext.dir")查看)指定的类加载到内存中
  • 系统(System)类加载器: 由AppClassLoader实现,负责加载系统类(环境变量%CLASSPATH%)指定,默认为当前路径的类加载到内存中。

除去以上三种外,还有一种比较特殊的线程上下文类加载器。存在于Thread类中,一般使用方式为new Thread().getContextClassLoader()

可以看出来,三种类型的加载器负责不同的模块的加载。那怎么才能保证我所使用的String就是JDK里面的String呢?这就是双亲委派模型的功能了:

上面三种类加载器中,他们之间的关系为:

也就是Bootstrap ClassLoader作为Extension ClassLoader的父类,而Extension ClassLoader作为Application ClassLoader的父类,Application ClassLoader是作为User ClassLoader的父类的。

而双亲委派机制规定:当某个特定的类加载在接收到类加载的请求的时候,首先需要将加载任务委托给父类加载器,依次递归到顶层后,如果最高层父类能够找到需要加载的类,则成功返回,若父类无法找到相关的类,则依次传递给子类。

补充:

  • 如果A类引用了B,则JVM将使用加载类A的加载器加载类B