一、类加载器
1 类加载过程
虚拟机只加载程序执行时所需要的类文件。假设程序从 MyProgram.class 开始运行,下面是虚拟机执行的步骤:
- 虚拟机有一个用于加载类文件的机制。
- 如果类拥有类型为另一个类的域,或者是拥有超类,那么这些类文件也会被加载。
- 接着,虚拟机执行类中的 main 方法。
- 如果 main 方法或者 main 调用的方法要用到更多的类,那么接下来就会加载这些类。
每个Java程序至少拥有三个类加载器:
启动类加载器(Bootstrap ClassLoader)
这个类加载器负责负责加载JDK中的核心类库,这个类加载器完全由JVM控制
扩展类加载器(Extendsion ClassLoader)
这个类加载器负责加载\lib\ext目录下的类库
应用程序类加载器(Application ClassLoader)
这个类加载器负责加载用户类路径(CLASSPATH)下的类库,一般我们编写的java类都是由这个类加载器加载
2 类加载器的层次结构
类加载器有一种父/子关系。除了引导类加载器外,每个类加载器都有一个父类加载器。父类加载失败子类才加载。
如果插件被打包为 JAR 文件,那就可以直接用 URLClassLoader 类的实例去加载这些类。
URL url = new URL("path"); |
可以通过下面将其设置成为任何类加载器
Thread t = Thread.currentThread(); |
助手方法可以获取这个上下文类加载器:
Thread t = Thread.currentThread(); |
当调用由不同的类加载器加载的插件类的方法时,进行上下文类加载器的设置是一种好的思路;或者,让助手方法的调用者设置上下文类加载器。
3 将类加载器作为命名空间
同一虚拟机中,可以有两个类的类名和包名都相同。类是由它的全名和类加载器来确定的。
4 编写自己的类加载器
编写自己的类加载器,只需要继承 ClassLoader 类,然后覆盖下面这个方法
findClass(String className) |
ClassLoader 超类的 loadClass 方法用于将类的加载操作委托给其父类加载器去进行,只有当该类尚未加载并且父类加载器也无法加载该类时,才调用 findClass 方法。
如果要实现该方法,必须做到以下几点:
- 为来自本地文件系统或者其他来源的类加载其字节码
- 调用 ClassLoader 超类的 defineClass 方法,想虚拟机提供字节码。
5 字节码校验
当类加载器将新在加载的 Java 平台类的字节码传递给虚拟机,这些字节码首先要接受校验器的校验。校验器负责检查那些指令无法执行的明细那有破坏性的操作。出了系统类外,所有的类都要被校验。
下面是校验器执行的一些检查:
- 变量要在使用之前进行初始化。
- 方法调用与对象引用类型之间要匹配。
- 访问私有类型和方法的规则没有被违反。
- 对本地变量的访问都落在运行时堆栈内。
- 运行时堆栈没有用溢出。
二、双亲委派原则
1 双亲委派原则
一个类加载器受到类加载的请求,它会把这个请求转交到它的父加载器去请求,如果上级还有加载器,就继续把请求上传,直到启动类加载器。然后找到就返回给子加载器,直到第一个发出请求的类加载器。如果最后还是没有找到,就让子加载器自己去找
2 破坏双亲委派原则
单一责任原则(SRP)不是绝对的。 它的存在有助于代码的可维护性和可读性。 但是您可能会不时看到解决方案,破坏SRP的模式,而且还可以。 其他原则也是如此,但是这次我想谈谈SRP。
在Java应用中存在着很多服务提供者接口(Service Provider Interface,SPI),这些接口允许第三方为它们提供实现,如常见的 SPI 有 JDBC、JNDI等,这些 SPI 的接口属于 Java 核心库,一般存在rt.jar包中,由Bootstrap类加载器加载,而 SPI 的第三方实现代码则是作为Java应用所依赖的 jar 包被存放在classpath路径下,由于SPI接口中的代码经常需要加载具体的第三方实现类并调用其相关方法,但SPI的核心接口类是由引导类加载器来加载的,而Bootstrap类加载器无法直接加载SPI的实现类,同时由于双亲委派模式的存在,Bootstrap类加载器也无法反向委托AppClassLoader加载器SPI的实现类。在这种情况下,我们就需要一种特殊的类加载器来加载第三方的类库,而线程上下文类加载器就是很好的选择。
2.1 线程上下文类加载器(contextClassLoader)
通过java.lang.Thread类中的getContextClassLoader()
和 setContextClassLoader(ClassLoader cl)
方法来获取和设置线程的上下文类加载器。如果没有手动设置上下文类加载器,线程将继承其父线程的上下文类加载器,初始线程的上下文类加载器是系统类加载器(AppClassLoader),在线程中运行的代码可以通过此类加载器来加载类和资源,如下图所示,以jdbc.jar加载为例