是时候介绍一下那些把Java类加载到内存中的家伙了
介绍
实现“通过一个类的全限定名来获取描述此类的二进制字节流”的代码模块
特性
Java语言的创新,领域包括:
类层次划分
OSGi(Open Service Gateway Initiative)
- Java动态化模块化系统的一系列规范
- 支持模块热部署,方便模块管理
热部署
代码加密
类与加载器
对于任意一个类,都需要由加载它的类加载器和这个类本身一同确定其在JVM中的唯一性
- 比较两个类是否相等,只有在这两个类是由同一个类加载器加载的前提下才有意义
- 相同Class文件
- 同一个JVM加载
- 不同类加载器加载
- 必定不相等
- Class对象的
equal()
、isAssignableFrom()
、isInstance()
- Class对象的
分类
启动类加载器Bootstrap ClassLoader
- C++实现
- 虚拟机自身的一部分
其他类加载器(扩展类加载器Extension ClassLoader、应用程序类加载器Application ClassLoader)
- Java实现
- 独立于虚拟机
- 继承抽象类
java.lang.ClassLoader
Bootstrap ClassLoader
加载路径
<JAVA_HOME>\lib
-Xbootclasspath
指定的路径中被虚拟机识别的(按名称)
说明
- 无法被Java程序直接引用
- 自定义加载器时,如果想使用此加载器加载,直接使用
null
即可
- 自定义加载器时,如果想使用此加载器加载,直接使用
- 无法被Java程序直接引用
Extension ClassLoader
- 加载路径
<JAVA_HOME>\lib\ext
java.ext.dirs
系统变量指定的路径中所有类库
- 说明
- 可以被Java程序直接使用
sun.misc.Launcher$ExtClassLoader
实现
Application ClassLoader
- 加载路径
- 用户类路径
ClassPath
所指定的类库
- 用户类路径
- 说明
- 可以被Java程序直接使用
sun.misc.Launcher$AppClassLoader
实现- ClassLoader中
getSystemClassLoader()
方法的返回值 - 如果没有自定义过类加载器,则默认此类加载器
双亲委派模型
- 启动类加载器Bootstrap ClassLoader
- 扩展类加载器Extension ClassLoader
- 应用程序类加载器Application ClassLoader
- 自定义类加载器User ClassLoader
- 自定义类加载器User ClassLoader
- 应用程序类加载器Application ClassLoader
- 扩展类加载器Extension ClassLoader
特点
- 除了顶层的启动类加载器外,其余皆有父类加载器
- 父子是组合(Composition)关系
- JDK 1.2+
工作流程
- 一个类加载器收到类加载的请求
- 逐层委派给父类加载器完成
- 传送到顶层的启动类加载器中
- 父类加载器没有找到时,子类加载器尝试自己加载
1 | //java.lang.ClassLoader#loadClass |
优点
- Java类随着类加载器一起具备了一种带有优先级的层次关系
- 保证了Java程序的稳定运行
破坏双亲委派模型
loadClass()
在JDK 1.2之前,双亲委派模型出现前,用户继承
java.lang.ClassLoader
唯一目的就是重写loadClass()
方法,因为虚拟机在进行类加载的时候会调用加载器的私有方法loadClassInternal()
,也就是调用自己的loadClass()
。双亲委派模型出现在JDK 1.2,而类加载器和抽象类
java.lang.ClassLoader
在JDK 1.0就有,为了向前兼容,在JDK 1.2中的java.lang.ClassLoader
增加了protected
的findClass()
方法,应当把自定义的类加载逻辑写到这个方法中,在loadClass()
里,如果父加载器加载失败,则会调用自己的findClass()
方法来完成加载,可以保证自定义的类加载器是符合双亲委派模型。
自身缺陷
基础类是作为被用户代码调用的API,但如果需要回调用户的代码时,Bootstrap ClassLoader不认识这些代码也就无法完成加载动作,怎么办呢? 例如JNDI服务
JNDI服务:
- JDK 1.3 :rt.jar
- 对资源进行集中管理和查找
- 需要调用独立厂商实现并部署在应用程序的
ClassPath
下的JNDI接口提供者(SPI)的代码
这时候就需要线程上下文类加载器(Thread Context ClassLoader) 来载入需要的Class
- 通过
java.lang.Thread
类的setContextClassLoader()
方法进行设置 - 创建线程时未设置,则从父线程中继承
- 如果全局都没有设置,默认为Application ClassLoader
线程上下文类加载器其实就是父类加载器委托子类加载器完成加载动作
- 父类加载器可以获得当前线程的Context ClassLoader,可能是它的子类加载器也可能是其他类加载器,父类加载器可以从其获得所需的Class,也就打破了只能向父类加载器请求的限制。
这个机制就满足了classpath是在运行时才确定,并且由定制的类加载器加载。由Application ClassLoader加载的class可以通过Thread Context ClassLoader获得定制的类加载器并载入特定的Class。
Java中所有涉及SPI的加载动作基本都采用这种方式,如JNDI、JDBC、JCE、JAXB和JBI等
追求程序的动态性
动态性:代码热替换HotSwap、模块热部署(Hot Deployment)
模块化之争:
Sun
JSR-294
(Java语言改进的模块化支持)JSR-277
(Java模块化系统)
JCP
最终,JCP赢了。不知道OSGI是啥,请看这篇文章
OSGI联盟给出的最新OSGI定义是The Dynamic Module System for Java,即面向Java的动态模块化系统。
OSGI实现模块化热部署的关键是自定义的类加载器机制的实现。
也就是说,每一个程序模块(Bundle)都有独自的类加载器,当需要更换一个Bundle时,就把Bundle和类加载器一起换掉,实现代码的热替换。
OSGI使用更为复杂的网状结构,不使用双亲委派模型的树状接口。
- 当收到类加载请求时,OSGI对类搜索的操作
- 将以
java.*
开头的类委派给父类加载器加载 - 否则,将委派列表名单内的类委派给父类加载器
- 否则,将Import列表中的类委派给Export这个类的Bundle的类加载器加载
- 否则,查找当前Bundle的ClassPath,使用自己的类加载器加载
- 否则,查找类是否在自己的Fragment Bundle中,如果在,则委派给Fragment Bundle的类加载器加载
- 否则,查找Dynamic Import列表的Bundle,委派给对应的Bundle的类加载器加载
- 否则,类查找失败
- 将以
想掌握类加载器的精髓,就搞懂OSGI的实现吧