类加载中的双亲委派模型

下文转自www.javathinker.net

类加载器简介

  在介绍双亲委托模型之前,先介绍一下类加载器。类加载器通过一个类的全限定名来转换为描述这个类的二进制字节流。

    对于任意一个类,被同一个类加载器加载后都是唯一的,但如果被不同加载器加载后,就不是唯一的了。即使是源于同一个Class文件、被同一个JVM加载,只要加载类的加载器不同,那么类就不同。

    如何判断类是否相同,可以使用Class对象的equals()方法、isAssignableFrom()方法、isInstance()方法的返回结果进行判断,也可以使用instanceof关键字进行对象所属关系的判断。

下面我们写一个不同类加载器加载后的类,看一下对instanceof关键字运算有什么影响:  

public class OneMoreStudy {

public static void main(String[] args) throws Exception {

ClassLoader myLoader = new ClassLoader() {

@Override

public Class loadClass(String name) throws ClassNotFoundException {

try {

String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class";

InputStream inputStream = getClass().getResourceAsStream(fileName);

if (inputStream == null) {

return super.loadClass(name);

}

byte[] array = new byte[inputStream.available()];

inputStream.read(array);

return defineClass(name, array, 0, array.length);

} catch (IOException e) {

throw new ClassNotFoundException(name);

}

}

};

Object object = myLoader.loadClass("OneMoreStudy").newInstance();

System.out.println("class name: " + object.getClass().getName());

System.out.println("instanceof: " + (object instanceof OneMoreStudy));

}

}

   运行结果:  

class name: OneMoreStudy

instanceof: false

   在运行结果中,第一行可以看出这个对象确实是

OneMoreStudy

类实例化出来的,但在第二行中instanceof运算结果是false,说明在JVM中存在两个

OneMoreStudy

类,一个是由系统应用程序类加载器加载的,另一个是由我们自定义的类加载器加载的。虽然都是来自同一个Class文件,在同一个JVM里,但是被不同的类加载器加载后,仍然是两个独立的类。      

类加载器的划分

  除了像上面例子代码中,我们自己实现的自定义类加载器,还有3种系统提供的类加载器:  

启动类加载器(Bootstrap ClassLoader):它负责将存放在%JAVA_HOME%\lib目录中的,或者被-Xbootclasspath参数所指定的路径中的,并且是JVM识别的类库加载到JVM内存中。它仅按照文件名识别,如rt.jar,名字不符合的类库即使放在lib目录中也不会被加载。它是由C++语言实现的,无法被Java程序直接引用。

扩展类加载器(Extension ClassLoader):它负责加载%JAVA_HOME%\lib\ext目录中的,或者被java.ext.dirs系统变量所指定的路径中的所有类库。它由sun.misc.Launcher.ExtClassLoader实现,开发者可以直接使用扩展类加载器。

应用程序类加载器(Application ClassLoader):它负责加载用户类路径(ClassPath)上所指定的类库。由于它是ClassLoader中的getSystemClassLoader()方法的返回值,所以一般也称它为系统类加载器。它由sun.misc.Launcher.AppClassLoader来实现,开发者可以直接使用这个类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。

双亲委托模型

  之前提到,对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立其在JVM中的唯一性。可是有这么多种的类加载器,如何保证一个类在JVM中的唯一性呢?为了解决这个问题,双亲委托模型(Parents Delegation Model)应运而生,它就是下图所展示的类加载器之间的层次关系:


    除了顶层的启动类加载器外,其余的类加载器都必须有自己的父类加载器。类加载器之间的父子关系,一般不会以继承的关系来实现,而是都使用组合关系来复用父类加载器。

    类加载器收到类加载的请求后,它不会首先自己去尝试加载这个类,而是把这个请求委派给父类加载器去尝试加载。每一个类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中。只有当父类加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。这样就保证了类在JVM中的唯一性,也保证了Java程序稳定运作。

    实现双亲委派模型的代码都集中在java.lang.ClassLoader的loadClass()方法之中,如下:  

protected Class loadClass(String name, boolean resolve)

throws ClassNotFoundException

{

synchronized (getClassLoadingLock(name)) {

//首先,检查该类是否已经被加载过了

Class c = findLoadedClass(name);

//如果没有加载过,就调用父类加载器的loadClass()方法

if (c == null) {

long t0 = System.nanoTime();

try {

if (parent != null) {

c = parent.loadClass(name, false);

} else {

//如果父类加载器为空,就使用启动类加载器

c = findBootstrapClassOrNull(name);

}

} catch (ClassNotFoundException e) {

//如果在父类加载器中找不到该类,就会抛出ClassNotFoundException

}

if (c == null) {

//如果父类找不到,就调用findClass()来找到该类。

long t1 = System.nanoTime();

c = findClass(name);

//记录统计数据

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设计者们推荐给开发者们的类加载器实现方式。大部分的类加载器都遵循这个模型,但也有例外的情况,比如下面这三种情况:  

重写ClassLoader的loadClass()方法

  在上面例子代码中,就是重写了ClassLoader的loadClass()方法,破坏了双亲委派模型,产生了不唯一的类。所以,不提倡开发人员覆盖loadClass()方法,而应当把自己的类加载逻辑写到findClass()方法中,在loadClass()方法的逻辑里如果父类加载失败,则会调用自己的findClass()方法来完成加载,这样就可以保证新写出来的类加载器是符合双亲委派模型。      

SPI(服务提供者接口)

  Java提供了很多SPI(Service Provider Interface,服务提供者接口),允许第三方为这些接口提供实现,常见的SPI有JDBC、JNDI、JCE、JAXB和JBI等。

    SPI的接口由Java核心库来提供,而这些SPI的实现代码则是作为Java应用所依赖的jar包被包含进类路径(ClassPath)里。SPI接口中的代码经常需要加载具体的实现类。那么问题来了,SPI的接口是Java核心库的一部分,是由启动类加载器来加载的;SPI的实现类是由系统类加载器来加载的。引导类加载器是无法找到SPI的实现类的,因为依照双亲委派模型,启动类加载器无法委派系统类加载器来加载类。

    这时候就会使用线程上下文类加载器(Thread Context ClassLoader),在JVM中会把当前线程的类加载器加载不到的类交给线程上下文类加载器来加载,直接使用Thread.currentThread().getContextClassLoader()来获得,默认返回的就是应用程序类加载器,也可以通过java.lang.Thread类的setContextClassLoader()方法进行设置。

    而线程上下文类加载器破坏了双亲委派模型,也就是父类加载器请求子类加载器去完成类加载的动作,但为了实现功能,这也是一种巧妙的实现方式。      

OSGi(开放服务网关协议)

  OSGi(Open Service Gateway Initiative,开放服务网关协议)技术是面向Java动态化模块化系统模型,程序模块(称为Bundle)无需重新引导可以被远程安装、启动、升级和卸载。实现程序模块热部署的关键则是它自定义的类加载器机制的实现。

    在OSGi中,类加载器不再是双亲委派模型中的树状结构,而是一个较为复杂的网状结构,类加载的规则简要介绍如下:  

若类属于java.*包,则将加载请求委托给父加载器

若类定义在启动委托列表(org.osgi.framework.bootdelegation)中,则将加载请求委托给父加载器

若类属于在Import-Package中定义的包,则框架通过ClassLoader依赖关系图找到导出此包的Bundle的ClassLoader,并将加载请求委托给此ClassLoader

若类资源属于在Require-Bundle中定义的Bundle,则框架通过ClassLoader依赖关系图找到此Bundle的ClassLoader,将加载请求委托给此ClassLoader

Bundle搜索自己的类资源( 包括Bundle-Classpath里面定义的类路径和属于Bundle的Fragment的类资源)

若类在DynamicImport-Package中定义,则开始尝试在运行环境中寻找符合条件的Bundle

  如果在经过上面一系列步骤后,仍然没有正确地加载到类资源,则会向外抛出类未发现异常。    

总结

  类加载器通过一个类的全限定名来转换为描述这个类的二进制字节流,可划分为启动类加载器扩展类加载器应用程序类加载器自定义类加载器。在双亲委托模型中,将上述各种类加载器组成一系列的父子关系,子类加载器先把类加载请求委派给父类加载器去尝试加载,父类加载器无法加载时子类加载器才自己尝试加载,这样保证了类在JVM中的唯一性。不过,也不遵循双亲委托模型的情况,比如:重写ClassLoader的loadClass()方法、SPI(服务提供者接口)、OSGi(开放服务网关协议)。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,076评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,658评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,732评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,493评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,591评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,598评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,601评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,348评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,797评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,114评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,278评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,953评论 5 339
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,585评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,202评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,442评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,180评论 2 367
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,139评论 2 352