Android Runtime双亲委托模型在ART中的实现(24)

Android Runtime双亲委托模型在ART中的实现

一、双亲委托模型基础概念

1.1 双亲委托模型的定义与核心思想

双亲委托模型是Java类加载机制的核心设计模式,其核心思想是:当一个类加载器收到类加载请求时,它首先不会自己尝试加载这个类,而是把请求委托给父类加载器去完成,依次向上递归,直到最顶层的类加载器 。只有当父类加载器无法完成加载任务时,子类加载器才会尝试自己去加载 。这种模型确保了类加载的层次性和一致性,避免了类的重复加载和安全隐患 。

1.2 模型的层次结构

在Java和Android的类加载体系中,双亲委托模型形成了一个层次分明的结构 。在Android中,主要的类加载器层次包括:

  • BootClassLoader:最顶层的类加载器,负责加载系统核心类库,如java.lang包下的类和Android框架的核心类 。
  • PathClassLoader:用于加载已安装应用的Dex文件,它的父类加载器是BootClassLoader
  • DexClassLoader:支持从任意路径加载Dex文件,同样以BootClassLoader为父类加载器 。

这种层次结构使得类加载请求能够从底层向上传递,确保核心类的一致性和安全性 。

1.3 模型带来的优势

双亲委托模型为类加载机制带来了多方面的优势 。首先,它保证了类的唯一性,例如java.lang.Object类无论在哪个类加载器中被请求加载,最终都是由BootClassLoader加载,确保系统中只有一个版本的Object类,避免了类冲突 。其次,它提高了系统的安全性,核心类库由最顶层的类加载器加载,防止恶意代码通过自定义类加载器替换系统核心类 。此外,通过缓存已加载的类,减少了重复加载的开销,提高了类加载的效率 。

二、Android类加载器体系架构

2.1 主要类加载器及其职责

在Android系统中,主要的类加载器包括BootClassLoaderPathClassLoaderDexClassLoader,它们各自承担着不同的职责 。

  • BootClassLoader:作为系统的根类加载器,负责加载Android系统的核心类库,这些类库位于/system/framework目录下,包含了Java语言核心类和Android框架的基础类 。它是ClassLoader类的内部类,由C++代码在VM启动时创建 。
  • PathClassLoader:继承自BaseDexClassLoader,用于加载已安装应用的Dex文件 。应用在安装过程中,APK文件会被解压到/data/app目录,PathClassLoader从该目录加载应用的主Dex文件(classes.dex)和可能存在的次级Dex文件 。
  • DexClassLoader:同样继承自BaseDexClassLoader,但它支持从任意路径加载Dex文件,包括外部存储等位置 。这使得它适用于插件化、热修复等需要动态加载类的场景 。

2.2 类加载器的继承关系

Android的类加载器之间存在明确的继承关系 。BootClassLoader是所有类加载器的根,它没有父类加载器 。PathClassLoaderDexClassLoader都继承自BaseDexClassLoader,而BaseDexClassLoader又继承自ClassLoader类 。这种继承关系为双亲委托模型的实现提供了基础,子类加载器可以通过父类引用调用父类的加载方法,实现委托加载 。

2.3 与Java类加载器体系的异同

Android的类加载器体系与Java标准的类加载器体系既有相似之处,也存在差异 。相似之处在于都采用了双亲委托模型,类加载器之间形成层次结构,核心类由顶层类加载器加载 。不同之处在于,Java标准体系中的顶层类加载器是Bootstrap ClassLoader(由C++实现),接下来是Extension ClassLoaderApplication ClassLoader 。而Android中没有Extension ClassLoader,取而代之的是PathClassLoaderDexClassLoader,并且Android使用Dex文件格式而非Java的Class文件格式,这导致类加载的具体实现有所不同 。

三、ART运行时环境概述

3.1 ART与Dalvik的区别

Android Runtime(ART)是Android 5.0(API级别21)及以后版本的默认运行时环境,取代了之前的Dalvik虚拟机 。与Dalvik相比,ART具有以下主要区别:

  • 预编译(AOT)机制:Dalvik使用JIT(Just-In-Time)编译,在应用运行时将字节码转换为机器码;而ART采用AOT(Ahead-Of-Time)编译,在应用安装时就将Dex字节码编译为机器码,提高了运行效率 。
  • 内存管理优化:ART对垃圾回收机制进行了优化,减少了GC暂停时间,提高了应用的响应性 。
  • 文件格式:ART使用.oat(Optimized Android)文件,它是Dex文件的优化版本,包含了预编译的机器码和元数据;而Dalvik使用.dex文件 。

这些区别使得ART在性能和用户体验上有了显著提升 。

3.2 ART的类加载机制特点

在ART中,类加载机制与Dalvik有一些不同之处 。由于ART采用AOT预编译,类加载过程中可能涉及到对预编译机器码的处理 。ART会在应用安装时对Dex文件进行优化,生成.oat文件,类加载时可以直接从.oat文件中获取预编译的机器码,提高加载和执行效率 。此外,ART对类加载的安全性和兼容性进行了增强,确保不同版本的类能够正确加载和运行 。

3.3 ART与双亲委托模型的关系

ART的类加载机制仍然遵循双亲委托模型,这是Java和Android类加载的核心原则 。在ART中,类加载器的层次结构和委托关系与Dalvik时代基本保持一致 。无论是BootClassLoaderPathClassLoader还是DexClassLoader,在加载类时都会先委托给父类加载器 。ART的实现确保了双亲委托模型在预编译环境下仍然有效,维护了类加载的一致性和安全性 。

四、双亲委托模型在ClassLoader类中的实现

4.1 ClassLoader类的核心结构

ClassLoader类是Android类加载器体系的基类,定义了类加载的基本框架和核心方法 。其核心结构包括:

  • parent字段:指向父类加载器的引用,用于实现委托加载 。
  • loadedClasses缓存:存储已加载类的缓存表,避免重复加载 。
  • loadClass方法:实现双亲委托模型的核心方法,负责处理类加载请求 。
  • findClass方法:在父类加载器无法加载类时,由子类加载器实现该方法来查找和加载类 。

4.2 loadClass方法源码解析

loadClass方法是实现双亲委托模型的关键,其源码如下:

protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
    // 同步锁,确保线程安全
    synchronized (getClassLoadingLock(name)) {
        // 首先检查类是否已经被加载
        Class<?> c = findLoadedClass(name);
        if (c == null) {
            long t0 = System.nanoTime();
            try {
                if (parent!= null) {
                    // 如果父类加载器不为null,委托父类加载器加载
                    c = parent.loadClass(name, false);
                } else {
                    // 父类加载器为null,尝试从Bootstrap类加载器加载
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
                // 父类加载器无法加载该类
            }

            if (c == null) {
                // 父类加载器未找到类,调用自身的findClass方法
                long t1 = System.nanoTime();
                c = findClass(name);
                // 记录类加载时间统计信息
                sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                sun.misc.PerfCounter.getFindClasses().increment();
            }
        }
        if (resolve) {
            // 如果需要解析类,则进行解析
            resolveClass(c);
        }
        return c;
    }
}

该方法的执行流程如下:

  1. 首先检查请求加载的类是否已经在缓存中(通过findLoadedClass方法)。
  2. 如果未缓存,则将加载请求委托给父类加载器。如果父类加载器存在,则调用其loadClass方法;如果父类加载器为null,则尝试从Bootstrap类加载器加载。
  3. 如果父类加载器成功加载了类,则返回该类;否则,调用自身的findClass方法尝试加载。
  4. 如果findClass方法成功加载了类,则返回该类;否则,抛出ClassNotFoundException异常。

4.3 findClass方法的作用与实现

findClass方法在ClassLoader类中是一个抽象方法,由子类实现,用于在父类加载器无法加载类时,查找和加载类 。在BaseDexClassLoaderPathClassLoaderDexClassLoader的父类)中,findClass方法的实现如下:

protected Class<?> findClass(String name) throws ClassNotFoundException {
    // 在DexPathList中查找类
    List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
    Class c = pathList.findClass(name, suppressedExceptions);
    if (c == null) {
        ClassNotFoundException cnfe = new ClassNotFoundException("Didn't find class \"" + name + "\" on path: " + pathList);
        for (Throwable t : suppressedExceptions) {
            cnfe.addSuppressed(t);
        }
        throw cnfe;
    }
    return c;
}

该方法通过DexPathList对象的findClass方法在Dex文件列表中查找类。DexPathList维护了一个Dex文件的列表,每个Dex文件对应一个DexFile对象,通过遍历这些DexFile对象来查找类 。

五、BootClassLoader的实现与作用

5.1 BootClassLoader的特殊地位

BootClassLoader是Android类加载器体系中的最顶层类加载器,它负责加载系统核心类库 。与其他类加载器不同,BootClassLoaderClassLoader类的内部类,由C++代码在VM启动时创建,无法被开发者直接访问 。它没有父类加载器,是整个类加载器层次结构的根 。

5.2 BootClassLoader的初始化过程

在Android系统启动过程中,当ART虚拟机初始化时,会通过JNI调用创建BootClassLoader实例 。具体来说,在dalvik.system.VMRuntime类中,有一个本地方法initialize,该方法会在VM初始化时被调用,其中会创建BootClassLoader

// VMRuntime.java
private native void initialize(ClassLoader bootClassLoader,
                              boolean enableHiddenApiChecks,
                              boolean ignoreHiddenApiExemptions);

在C++层,对应的JNI方法会创建BootClassLoader实例并设置其类加载路径:

// 在JNI层实现
static void VMRuntime_initialize(JNIEnv* env, jobject javaThis, jobject bootClassLoader,
                               jboolean enableHiddenApiChecks,
                               jboolean ignoreHiddenApiExemptions) {
    // 获取JavaVM实例
    JavaVM* vm = nullptr;
    env->GetJavaVM(&vm);
    
    // 创建BootClassLoader实例
    jclass bootClassLoaderClass = env->FindClass("dalvik/system/BootClassLoader");
    jmethodID getInstance = env->GetStaticMethodID(bootClassLoaderClass, "getInstance", "()Ldalvik/system/BootClassLoader;");
    jobject bootClassLoaderInstance = env->CallStaticObjectMethod(bootClassLoaderClass, getInstance);
    
    // 设置BootClassLoader的类加载路径
    // ... 其他初始化操作
}

BootClassLoader的类加载路径通常是/system/framework目录下的核心类库文件,这些文件包含了Java和Android的基础类 。

5.3 BootClassLoader在双亲委托中的角色

在双亲委托模型中,BootClassLoader扮演着最顶层的委托者角色 。当其他类加载器(如PathClassLoaderDexClassLoader)收到类加载请求时,会逐级向上委托,最终到达BootClassLoader 。如果请求加载的类属于系统核心类库,BootClassLoader会尝试加载该类 。只有当BootClassLoader无法找到对应的类时,请求才会逐级返回,由下一级类加载器尝试加载 。这种机制确保了系统核心类的一致性和安全性 。

六、PathClassLoader与DexClassLoader的委托实现

6.1 PathClassLoader的委托机制

PathClassLoader是Android应用最常用的类加载器,用于加载已安装应用的Dex文件 。在实现双亲委托模型时,PathClassLoader通过其父类BaseDexClassLoader继承了ClassLoaderloadClass方法 。当PathClassLoader收到类加载请求时,首先会检查该类是否已被加载,如果未加载,则将请求委托给父类加载器(通常是BootClassLoader) 。只有当父类加载器无法加载该类时,PathClassLoader才会尝试从自己的Dex文件路径中查找并加载 。

6.2 DexClassLoader的委托机制

DexClassLoaderPathClassLoader类似,同样继承自BaseDexClassLoader,因此也遵循相同的双亲委托模型 。不同的是,DexClassLoader支持从任意路径加载Dex文件,适用于动态加载场景 。当DexClassLoader收到类加载请求时,同样会先委托给父类加载器,只有在父类加载器无法加载时,才会从自己指定的路径中查找类 。

6.3 二者委托实现的异同点

PathClassLoaderDexClassLoader在委托实现上的相同点是都遵循双亲委托模型,通过父类BaseDexClassLoader继承的loadClass方法实现委托加载 。不同点在于它们的类加载路径和适用场景 。PathClassLoader的路径固定为应用安装目录,适用于加载已安装应用的类;而DexClassLoader的路径可以由开发者指定,适用于动态加载外部Dex文件 。此外,DexClassLoader在加载类时可能需要进行额外的优化处理,如将Dex文件复制到优化目录 。

七、ART中类加载的JNI实现

7.1 JNI层与Java层的交互

在ART中,类加载涉及到JNI层与Java层的紧密交互 。当Java层的类加载器需要加载类时,某些操作(如查找预编译的机器码)需要通过JNI调用到C++层实现 。例如,在BaseDexClassLoaderfindClass方法中,会通过DexPathList对象查找类,而DexPathList的实现会涉及到JNI调用 。

7.2 关键JNI方法解析

在ART的JNI层,有几个关键方法与类加载密切相关:

  • FindClass:在JNI环境中查找类的方法,Java层的类加载器最终会调用该方法来查找类 。
  • DefineClass:将类的字节码定义为Class对象的方法,用于将从Dex文件中读取的字节码转换为Java可使用的Class对象 。
  • RegisterNatives:注册本地方法的方法,用于将Java方法与C++实现关联起来 。

这些JNI方法在类加载过程中起到了桥梁作用,使得Java层的类加载器能够与底层的ART运行时环境进行交互 。

7.3 类加载过程中的JNI调用流程

在类加载过程中,JNI调用流程大致如下:

  1. 当Java层的类加载器(如PathClassLoader)需要加载类时,会调用findClass方法 。
  2. findClass方法通过DexPathList对象查找类,DexPathList会遍历其维护的Dex文件列表 。
  3. 对于每个Dex文件,会通过JNI调用到C++层的DexFile类,查找对应的类定义 。
  4. 如果找到类定义,会获取类的字节码或预编译的机器码 。
  5. 通过JNI调用DefineClass方法,将字节码或机器码转换为Java的Class对象 。
  6. Class对象返回给Java层,完成类加载过程 。

这种JNI调用流程确保了Java层的类加载器能够利用ART的底层功能,高效地加载类 。

八、预编译机制对双亲委托的影响

8.1 ART预编译机制概述

ART的预编译机制是其与Dalvik的主要区别之一 。在应用安装过程中,ART会使用dex2oat工具将Dex字节码编译为机器码,生成.oat文件 。这个.oat文件包含了应用的所有类的预编译机器码和元数据,使得应用在运行时可以直接执行机器码,提高了性能 。

8.2 预编译对类加载流程的改变

预编译机制对类加载流程产生了一定影响 。在Dalvik时代,类加载主要是从Dex文件中读取字节码,然后解释执行或通过JIT编译为机器码 。而在ART中,类加载时可以直接从.oat文件中获取预编译的机器码,无需再次编译 。这使得类加载过程更加高效,但也增加了查找和验证预编译机器码的步骤 。

8.3 双亲委托模型在预编译环境下的适应性

双亲委托模型在预编译环境下仍然有效 。虽然类加载过程中涉及到预编译机器码的处理,但委托加载的逻辑并没有改变 。当类加载器收到类加载请求时,仍然会先委托给父类加载器,只有在父类加载器无法加载时,才会尝试从自己的预编译文件中查找类 。ART的实现确保了双亲委托模型在预编译环境下的适应性,维护了类加载的一致性和安全性 。

九、双亲委托模型的安全机制

9.1 防止类的重复加载

双亲委托模型的一个重要安全机制是防止类的重复加载 。由于类加载请求会首先向上委托,当某个类已经被顶层类加载器(如BootClassLoader)加载后,其他类加载器不会再次加载该类 。例如,java.lang.Object类由BootClassLoader加载后,任何其他类加载器请求加载Object类时,都会直接使用已加载的版本,确保系统中只有一个版本的Object类,避免了类冲突和安全隐患 。

9.2 保护核心类库安全

双亲委托模型通过将核心类库的加载权限限制在顶层类加载器,保护了核心类库的安全 。例如,java.lang包下的类只能由BootClassLoader加载,即使开发者自定义了一个名为java.lang.String的类,也无法通过自定义类加载器加载,因为委托加载机制会首先将请求委托给BootClassLoader,而BootClassLoader会从系统核心类库中加载真正的String类 。这种机制防止了恶意代码通过替换核心类来破坏系统安全 。

9.3 安全漏洞与防范措施

尽管双亲委托模型提供了一定的安全保障,但仍然存在一些潜在的安全漏洞 。例如,开发者可以通过自定义类加载器打破双亲委托模型,直接加载自己的类,可能导致类冲突或安全风险 。为防范这些漏洞,Android系统采取了多种措施 。例如,限制对系统核心类库路径的访问权限,确保只有系统类加载器能够加载这些路径下的类;对类加载过程进行严格的权限检查,防止非法类的加载 。此外,Android还提供了安全沙箱机制,隔离不同应用的运行环境,进一步增强系统安全性 。

十、双亲委托模型的性能优化

10.1 类加载缓存机制

为提高类加载性能,ART在双亲委托模型的基础上实现了类加载缓存机制 。在ClassLoader类中,维护了一个已加载类的缓存表(loadedClasses),用于存储类名和对应的Class对象 。当类加载器收到类加载请求时,首先会检查这个缓存表,如果类已经被加载,则直接返回缓存中的Class对象,避免了重复加载的开销 。这种缓存机制在应用运行过程中能够显著提高类加载的效率,尤其是对于频繁使用的类 。

10.2 预编译与缓存的协同作用

ART的预编译机制与类加载缓存机制协同作用,进一步提高了性能 。预编译将Dex字节码转换为机器码,使得类加载时可以直接执行机器码,减少了字节码解释或JIT编译的时间 。而缓存机制则避免了重复加载相同的类,减少了文件读取和解析的开销 。两者结合,使得类加载过程更加高效,应用启动速度和运行性能都得到了提升 。

10.3 委托链长度对性能的影响

双亲委托模型中的委托链长度会对性能产生一定影响 。如果委托链过长,每次类加载都需要经过多个类加载器的委托和查找,会增加类加载的时间 。因此,在设计类加载器层次结构时,应尽量避免不必要的层级,保持委托链的简洁 。在Android中,类加载器的层级结构相对简单,主要是BootClassLoaderPathClassLoaderDexClassLoader,这种简洁的结构有助于提高类加载的性能 。

十一、双亲委托模型的应用场景

11.1 系统核心类加载

双亲委托模型最主要的应用场景是系统核心类的加载 。Android系统的核心类库(如java.langandroid.app等包下的类)由BootClassLoader加载,确保这些类在系统中只有一个版本,并且不会被恶意代码替换 。当应用需要使用这些核心类时,通过双亲委托机制,最终会由BootClassLoader加载,保证了系统的稳定性和安全性 。

11.2 应用类加载

普通Android应用的类由PathClassLoader加载 。当应用启动时,PathClassLoader会从应用安装目录加载主Dex文件和次级Dex文件中的类 。在加载过程中,遵循双亲委托模型,先委托给BootClassLoader加载系统核心类,只有在父类加载器无法加载时,才会从应用自身的Dex文件中加载 。这种机制确保了应用类与系统核心类的隔离和协作 。

11.3 插件化与热修复技术

在插件化和热修复技术中,双亲委托模型也发挥着重要作用 。插件化开发中,通常使用DexClassLoader加载插件的Dex文件 。当插件需要使用系统类或主应用类时,通过双亲委托机制,会先委托给父类加载器(如BootClassLoader或主应用的PathClassLoader)加载,确保插件与主应用使用相同的系统类版本,避免类冲突 。在热修复中,通过自定义类加载器加载修复后的类,同样遵循双亲委托模型,确保修复类能够正确替换有问题的类 。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Android 小码蜂

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值