JVM系列之四:类装载器
class装载验证流程
加载
– 装载类的第一个阶段
– 取得类的二进制流
– 转为方法区数据结构
– 在Java堆中生成对应的java.lang.Class对象
链接
– 验证
· 目的:保证Class流的格式是正确的
· 文件格式的验证
· 是否以0xCAFEBABE开头
· 版本号是否合理
· 元数据验证
· 是否有父类
· 继承了final类?
· 非抽象类实现了所有的抽象方法
· 字节码验证 (很复杂)
· 运行检查
· 栈数据类型和操作码数据参数吻合
· 跳转指令指定到合理的位置
· 符号引用验证
· 常量池中描述类是否存在
· 访问的方法或字段是否存在且有足够的权限
– 准备
· 分配内存,并为类设置初始值 (方法区中)
· public static int v=1;
· 在准备阶段中,v会被设置为0
· 在初始化的<clinit>中才会被设置为1
· 对于static final类型,在准备阶段就会被赋上正确的值
· public static final int v=1;
– 解析
· 符号引用替换为直接引用
初始化
– static变量 赋值语句
– 执行类构造器<clinit>
· static变量 赋值语句
· static{}语句
– 子类的<clinit>调用前保证父类的<clinit>被调用
– <clinit>是线程安全的
什么是类装载器ClassLoader
ClassLoader是一个抽象类
ClassLoader的实例将读入Java字节码将类装载到JVM中
ClassLoader可以定制,满足不同的字节码流获取方式
ClassLoader负责类装载过程中的加载阶段
JDK中ClassLoader默认设计模式
ClassLoader的重要方法
– public Class<?> loadClass(String name) throws ClassNotFoundException
• 载入并返回一个Class
– protected final Class<?> defineClass(byte[] b, int off, int len)
• 定义一个类,不公开调用
– protected Class<?> findClass(String name) throws ClassNotFoundException
• loadClass回调该方法,自定义ClassLoader的推荐做法
– protected final Class<?> findLoadedClass(String name)
• 寻找已经加载的类
分类
– BootStrap ClassLoader (启动ClassLoader)
• 只加载rt.jar中的类或者通过-Xbootclasspath设置该路径下的所有类
– Extension ClassLoader (扩展ClassLoader)
• 只加载ext里面的所有类
– App ClassLoader (应用ClassLoader/系统ClassLoader)
• 加载ClassPath下的所有类,也就是自己编写的类
– Custom ClassLoader(自定义ClassLoader)
每个ClassLoader都有一个Parent作为父亲
协同工作
双亲委派模式
使用双亲委派模型来组织类加载器之间的关系,好处在于Java类随着它的类加载器一起具备了一种带有优先级的层次关系。
例如类java.lang.Object,它存在在rt.jar中,无论哪一个类加载器要加载这个类,最终都是委派给处于模型最顶端的启动类加载器进行加载,因此Object类在程序的各种类加载器环境中都是同一个类。
相反,如果没有双亲委派模型而是由各个类加载器自行加载的话,用户编写了一个java.lang.Object的类,并放在程序的ClassPath中,那系统中将会出现多个不同的Object类,程序将混乱。因此,如果开发者尝试编写一个与rt.jar类库中已有类重名的Java类,将会发现可以正常编译,但是永远无法被加载运行
双亲委派模型的系统实现:
双亲委派模型对于保证Java程序的稳定运作很重要,但它的实现却很简单,实现集中在java.lang.ClassLoader的loadClass()
方法中,在其方法中,主要判断逻辑如下:先检查是否已经被加载过,
· 若没有被加载过,则接着判断父加载器是否为空。
o 若不为空,则调用父类加载器的loadClass()方法。
o 若父加载器为空,则默认使用启动类加载器作为父加载器。
· 如果父加载失败,则抛出ClassNotFoundException异常后,再调用自己的findClass()方法进行加载。
问题
解决
– 上下文加载器
– 是一个角色
– 用以解决顶层ClassLoader无法访问底层ClassLoader的类的问题
– 基本思想是,在顶层ClassLoader中,传入底层ClassLoader的实例
– 示例
- static private Class getProviderClass(String className, ClassLoader cl,
- boolean doFallback, boolean useBSClsLoader) throws ClassNotFoundException
- {
- try {
- if (cl == null) {
- if (useBSClsLoader) {
- return Class.forName(className, true, FactoryFinder.class.getClassLoader());
- } else {
- cl = ss.getContextClassLoader();
- if (cl == null) {
- throw new ClassNotFoundException();
- }
- else {
- return cl.loadClass(className); //使用上下文ClassLoader
- }
- }
- }
- else {
- return cl.loadClass(className);
- }
- }
- catch (ClassNotFoundException e1) {
- if (doFallback) {
- // Use current class loader - should always be bootstrap CL
- return Class.forName(className, true, FactoryFinder.class.getClassLoader());
- }
- …..
代码来自于javax.xml.parsers.FactoryFinder,展示如何在启动类加载器加载AppLoader的类。
上下文ClassLoader可以突破双亲模式的局限性。
打破常规模式
双亲模式的破坏
– 双亲模式是默认的模式,但不是必须这么做
– Tomcat的WebappClassLoader 就会先加载自己的Class,找不到再委托parent
– OSGi的ClassLoader形成网状结构,根据需要自由加载Class