Java类加载器及Android类加载器基础

引子

Android插件化与热更新技术日渐成熟,当你研究这些技术时会发现类加载器在其中占据重要地位。Java语言天生就有灵活性、动态性,支持运行期间动态组装程序,而这一切的基础就是类加载器。

Java中的类加载器

Java灵活性和动态性的原因

Java源代码被编译器编译成字节码,即从.java文件编译为.class文件,而.class文件就是通过类加载器加载到虚拟机内存中的。

虚拟机的类加载(Class Loading)过程分为加载、链接(验证、准备、解析)、初始化、使用、卸载等过程。这里仅考虑加载这个阶段,在此阶段虚拟机的工作有以下几点:

  1. 通过一个类的全限定名来获取该类的二进制字节流
  2. 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
  3. 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口

注意看第1条,虚拟机规范只是说来获取一个类的二进制字节流,但并没有说从哪里获取,怎样获取,这也就意味着Class文件可以来自磁盘、ZIP文件、JAR文件、数据库、甚至来自网络或者在程序运行时动态生成。上述各种来源的Class文件都是由类加载器(Class Loader)来加载的,也正因为如此,Java才拥有高度的灵活性和动态性。

Java中的几种类加载器

Java中的类加载器至少有三种:

  • 启动类加载器——该加载器一般由C或C++实现(如HotSpot用C++实现,其实也有虚拟机是用Java实现的),它是作为虚拟机不可分割的一部分而存在。该加载器负责加载jre/lib中的系统类,如通常从rt.jar中进行加载。启动类加载器由于属于虚拟机的一部分,因此无法被Java程序直接引用,所以例如String.class.getClassLoader()将会返回null。
  • 扩展类加载器——该加载器由Java语言实现,继承自java.lang.ClassLoader,独立于虚拟机外部,负责加载jre/lib/ext目录下的文件,如果对扩展类加载器调用getParent()也会返回null。
  • 系统类加载器(或叫应用类加载器)——该加载器由Java语言实现,继承自java.lang.ClassLoader,独立于虚拟机外部,负责加载应用程序类。如果应用程序中没有自定义的类加载器,那么此加载器就是默认的类加载器。

此外,用户还可以继承ClassLoader类来自定义类加载器,这样就可以在向虚拟机传递字节码之前进行需求定制了。

注意:对于任意一个Java类,它在虚拟机里的唯一性是由其类本身及其类加载器共同决定的。如果两个类来自同一个Class文件,在同一个虚拟机中,但是被不同的ClassLoader所加载,那么这两个类在虚拟机中也是不相等的。

类加载器的双亲委派模型

先来看下Java中的类加载器层次关系:

这里写图片描述

上述层次关系称为类加载器的双亲委派模型,它是在JDK 1.2中引入的,其实它并非强制性的约束,而是推荐我们使用的一种类加载机制,可以看到除了顶部的启动类加载器之外,其他加载器都有一个父类加载器。

双亲委派模型的工作流程:当一个类加载器收到加载类的请求时,它自己先不进行加载,而是把该请求委派为父类加载器去完成,父类加载器也是如此,直到将加载类的需求传给顶层的启动类加载器;只有当父类加载器无法完成加载时(在自己的搜索范围中没有找到该类),子加载器才尝试自己去完成类加载,如果加载不了,则会抛出ClassNotFoundException异常。

有一点需要注意:如果扩展类加载器收到请求去加载一个类,它会先委托启动类加载器去加载,如果启动类加载器加载不了,则尝试自己加载。如果扩展类加载器也无法加载,则直接抛出ClassNotFoundException异常而结束,并不会再交给下一层的应用类加载器去加载。

说明了双亲委派模型的原理后,再来看下其源码实现,代码逻辑很简单,也证实了上述讲到的双亲委派模型的工作流程:

public Class<?> loadClass(String name) throws ClassNotFoundException {
    return loadClass(name, false);
}

protected synchronized Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException {
    // 首先判断该类是否已经被加载过,如果已加载过就直接返回
    Class c = findLoadedClass(name);
    if (c == null) {
        // 如果没有被加载,就委托给父加载器处理或者给启动类加载器处理
        try {
            if (parent != null) {
                // 如果存在父类加载器,就委派给父类加载器加载  
                c = parent.loadClass(name, false);
            } else {
                // 如果不存在父类加载器,就检查是否由启动类加载器加载  
                // 通过调用native方法 findBootstrapClass0(String name)  
                c = findBootstrapClass0(name);
            }
        } catch (ClassNotFoundException e) {
            // 如果父加载器和启动类加载器都不能完成加载任务,自身才尝试去加载
            c = findClass(name);
        }
    }
    if (resolve) {
        resolveClass(c);
    }
    return c;
}
双亲委派模型的意义

使用双亲委派模型来组织各种类加载器,使之遵循了一定的优先级层次,从而能保证Java运行环境的稳定与条理性。例如java.lang.Object类是所有类的基类,并且根据双亲委派模型它是由启动类加载器加载的,如果我们也自定义了一个java.lang.Object类(只是假如,其实虚拟机会对java.lang开头的自定义类抛异常)并放在应用程序的ClassPath中去加载,那么应用中就会出现多个Object类,从而会导致Java类型体系混乱而无法正常运行。

另一个好处是避免类的二次加载。从上述loadClass源码中可知,先判断该类是否被加载过,如果已被加载过则直接返回该类。当一个类加载器委托父类加载时也是执行此逻辑,从而保证某些类只被加载一次。

自定义类加载器

由于自定义类加载器通常继承ClassLoader,来看下ClassLoader的几个主要方法:

// 加载指定完整名称的二进制字节流,不建议子类加载器重写,否则可能会破坏双亲委派模型
public Class<?> loadClass(String name) throws ClassNotFoundException{ … }

// 加载指定完整名称的二进制字节流,不建议子类加载器重写,否则可能会破坏双亲委派模型
protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException{ … }

// 被loadClass方法调用去加载指定名称类,官方建议子类加载器重写该方法
protected Class<?> findClass(String name) throws ClassNotFoundException { … }

// 该方法将二进制字节流转换为Class,一般在findClass方法中读取到对应字节码后调用,由于是final方法,故不可继承,其功能具体由虚拟机
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值