一、类加载器
类加载器是分层级的,遵循**双亲委派**机制:
最上层是**Bootstrap ClassLoder**,加载java的**核心类库**,加载java安装目录下的**lib目录**的class文件
第二层是**Ext ClassLoder**,加载一些java的**其他类库**,加载java安装目录下的**lib/ext**目录下的class
第三层是**Application ClassLoder** ,应该程序类加载器,这个类加载器是加载我们写的类
如果我们**自定义类加载器**的话,那就是第四层
二、双亲委派机制
当一个类加载器收到了一个类加载请求时,它自己不会先去尝试加载这个类,而是把这个请求转交给父类加载器,每一个层的类加载器都是如此,因此所有的类加载请求都应该传递到最顶层的启动类加载器中。只有当父类加载器在自己的加载范围内没有搜寻到该类时,并向子类反馈自己无法加载后,子类加载器才会尝试自己去加载。
加载标准类库与用户代码,会有不同的方式:
举个例子:
就以自定义的com.test.A类为例,我们来看看这个类是如何被类加载器加载的:
第一步: 首先是由AppClassLoader类加载器去查找com.test.A类, 他要去看他已经加载的类中是否有这个类, 如果有, 就直接返回, 如果没有, 就去加载这个类,但不是由AppClassLoader类加载器直接加载。而是委托他的父类也就是ExtClassLoader扩展类加载器去加载。
第二步:ExtClassLoader类加载器也是先搜索,查看已经加载的类是否有com.test.A类, 如果有就返回,如果没有就加载这个类。在加载的时候,也不是由自己来加载,而是委托他的父类,BootstrapClassLoader类加载器去加载。
第三步:BootstrapClassLoader类加载器先查找已经加载的类中是否有这个类,有则返回,没有就去加载这个类。这时候, 我们都知道, A类是我自己定义的, BootstrapClassLoader类加载器中不可能有, 加载失败,所以, 他就会去加载这个类。回去扫描/lib/jar包中有没有这个类,发现没有,于是让ExtClassLoader扩展类加载器去加载, ExtClassLoader扩展类加载器会去扫描扩展包lib/jar/ext包,里面有没有呢? 当然也没有, 于是委托AppClassLoader类加载器, ok, AppClassLoader类加载器是有的, 于是就可以加载了, 然后返回这个类。
【通过分析,我们可以得出,双亲委派机制的实现使用的是责任链设计模式。】
那么, 这里有一个问题, 那就是, 由AppClassLoader类加载器首先加载, 然后最后又回到了AppClassLoader类加载器. 绕了一圈又回来了, 这样是不是有些多此一举呢, 循环了两次? 为什么一定要从AppClassLoader类加载器加载呢? 直接从BootstrapClassLoader类加载器加载不好么?只循环一次啊....
其实, 对于我们的项目来说, 95%的类都是我们自己写的, 因此, 而我们自己写的类是有AppClassLoader类加载器加载. 其实AppClassLoader类加载器只有在第一次的时候, 才会加载两次. 以后, 当再次使用到这个类的时候, 直接去问AppClassLoader类加载器, 有这个类么? 已经有了, 就直接返回了.
三、双亲委派存在的意义【为什么使用它】
两个原因:
1. 沙箱安全机制, 自己写的java.lang.String.class类不会被加载, 这样便可以防止核心API库被随意修改.
2. 避免类重复加载. 比如之前说的, 在AppClassLoader里面有java/jre/lib包下的类, 他会加载么? 不会, 他会让上面的类加载器加载, 当上面的类加载器加载以后, 就直接返回了, 避免了重复加载.
四、双亲委派机制有什么缺陷?
检查类是否加载的委派过程是单向的, 这个方式虽然从结构上说比较清晰,使各个 ClassLoader 的职责非常明确, 但是同时会带来一个问题, 即顶层的ClassLoader 无法访问底层的 ClassLoader 所加载的类。
五、历史上有3次打破双亲委派机制
第一次被破坏:
在 jdk 1.2 之前,那时候还没有双亲委派模型,不过已经有了 ClassLoader 这个抽象类,所以已经有人继承这个抽象类,为了向前兼容
第二次破坏:
第二次破坏指的是 JNDI、JDBC 之类的情况。
首先得知道什么是 SPI(Service Provider Interface),它是面向拓展的,也就是说我定义了个规矩,就是 SPI ,具体如何实现由扩展者实现。
像我们比较熟的 JDBC 就是如此。
MySQL 有 MySQL 的 JDBC 实现,Oracle 有 Oracle 的 JDBC 实现,我 Java 不管你内部如何实现的,反正你们这些数据库厂商都得统一按我这个来,这样我们 Java 开发者才能容易的调用数据库操作,所以在 Java 核心包里面定义了这个 SPI。
查看ServiceLoader.load(Driver.class);方法发现类加载器使用的是线程上下文类加载器,这是打破双亲委托机制的关键。
第三次破坏:
这次破坏是为了满足热部署的需求,不停机更新这对企业来说至关重要,毕竟停机是大事。
OSGI 就是利用自定义的类加载器机制来完成模块化热部署,而它实现的类加载机制就没有完全遵循自下而上的委托,有很多平级之间的类加载器查找,具体就不展开了,有兴趣可以自行研究一下。
本文详细解析了Java中的双亲委派机制,包括类加载器的层级、工作原理以及为何采用该机制。此外,还探讨了机制存在的意义,如安全性和避免重复加载。同时,文中提到了双亲委派机制的不足之处,并列举了历史上三次打破这一机制的实例:SPI(如JNDI、JDBC)、线程上下文类加载器和服务热部署(如OSGI)。
955

被折叠的 条评论
为什么被折叠?



