在Java历史上,有过三次这种事件发生, 第一次被破坏,还是在双亲委派模型出现之前, 即JDK1.2 面世以前的“远古”时代。 由于双亲委派 模型在JDK1.2 之后 才被引入, 但是类加载器的概念和抽象类 java.lang.ClassLoader 则在 Java的 第一个版本中就已经存在, 面对已经存在的用户自定义类加载器的代码, Java 设计者不得不妥协, 为兼容这些代码, 无法再以技术手段避免loadClass() 被子类覆盖的可能性, 只能在JDK1.2 之后的java.lan g.ClassLoader 中添加一个新的protected 方法 findClass() , 并引导用户编写的类加载逻辑是尽可能去重写这个方法, 而不是在loadClass() 中编写代码。 按照loadClass() 方法的逻辑,如果父类加载失败, 会自动调用自己的findClass()方法来完成加载,这样既不影响用户按照自己的意愿去加载类, 又可以保证新写出来的类加载器是符合双亲委派规则的。
第二次被破坏的是 由这个模型的缺陷导致的, 双亲委派很好地解决了各个类加载器协作时基础类型的一致性问题(越基础的类由越上层的加载器进行加载),基础类型之所以被称为“基础”,是因为他们总是作为被用户代码继承、调用的API存在,但程序设计往往没有绝对不变的完美规则,如果有基础类型又要调回用户的用户的代码,该怎么办?
JNDI服务, JNDI服务是Java的标准服务, 它的代码启动类加载器来完成加载(在jdk1.3时加入到rt.jar的),肯定属于Java中很基础的类型了。但JDNI存在的目的就是对资源进行查找和集中管理,他需要调用其他厂商实现并部署在应用程序的ClassPath下的JNDI服务提供者接口(Service Provider Interface,SPI)的代码。启动类加载器不可能认识, 加载这些代码,该怎么办?
为了解决, Java设计团队引入了一个不太优雅的设计:线程上下文类加载器(Thread Context ClassLoader)。这个类加载器可以通过java.lang.Thread类的setContextClassLoader()方法进行设置,如果创建线程时还未设置, 他将会从父线程中继承一个,如果在应用程序的全局范围内都没有设置过的话, 那这个类加载器默认使用的就是应用程序类加载器。
为了解决这种不优雅的设计, 在JDK6时,jdk提供了 java.util.ServiceLoader类,在META-INF/services中的配置信息,辅以责任链模式, 这才算是给SPI的加载器提供了一种相对合理的解决方案。
第三次破坏是由于 用户对程序动态性的追求而导致的,这里所说的“动态性”指的是一些非常“热”门的名词:代码热替换(Hot Swap)、模块热部署(Hot Deployment)等。说白了就是希望Java应用程序能想我们的电脑外设那样,接上鼠标、U盘,不用重启机器就能立即使用,鼠标有问题或要升级就换个鼠标,不用关机也不用重启。
我们在开发中,遇到比较多的,网上也说的比较多的是,web容器 tomcat的加载,是破坏了双亲委派机制。
参考文献 >> 《深入Java 虚拟机》第三版
这本书写的很好, 有时间的朋友可以去看看。
收录于合集 #jvm
3个
上一篇JVM 类加载机制