类加载器与双亲委派机制

类加载器

  在Java虚拟机(JVM)中,类加载器(Class Loader)是负责将字节码文件加载到内存中并转换为类对象的组件。类加载器的主要任务是将 .class 文件加载到 JVM 中,以便程序能够使用这些类。JVM中的类加载器机制是Java平台的关键部分,它实现了Java的动态类加载特性。

JVM的类加载器

  • Bootstrap ClassLoader(引导类加载器)
      这是最基础的类加载器,由JVM自身实现,负责加载JRE核心库中的类(例如 java.lang.、java.util. 等)。引导类加载器直接使用本地代码来实现,它不会继承自 java.lang.ClassLoader 类。引导类加载器负责加载/lib目录下的类。

  • Extensions ClassLoader(扩展类加载器)
      Java中的实现类为ExtClassLoader,负责加载Java扩展库中的类,通常位于 JAVA_HOME/lib/ext 目录下。它是由 java.lang.ClassLoader 类的子类实现的。扩展类加载器用于加载一些标准扩展的类库,如加密、图形处理等功能扩展包。

  • Application ClassLoader(应用程序类加载器)
      Java中的实现类为AppClassLoader,是与我们接触最多的类加载器,开发人员写的代码默认由它来加载,ClassLoader.getSystemClassLoader返回的就是它。也称为系统类加载器,负责加载应用程序的类路径(classpath)下的类,包括我们编写的自定义类以及第三方库。它通常是类加载层次结构中最高级别的加载器,大多数应用程序的类加载都通过该加载器来完成。

类加载器的工作机制

Java中类的加载过程分为三个阶段:加载、连接 和 初始化。

  • 加载:通过类的全限定名查找字节码文件,并将其加载到内存中,生成 java.lang.Class 对象。
  • 连接:将类文件中的符号引用解析为直接引用,并进行类的验证、准备和解析等过程。
  • 初始化:为类的静态变量赋值,并执行静态初始化块(static 块)。

双亲委派机制

  类加载器采用了双亲委派模型,即一个类加载器在加载类时,会首先委托其父加载器来加载,只有当父加载器无法找到该类时,才会尝试自己加载。这一机制确保了Java核心类库的安全性和一致性,避免了类的重复加载。

  • Bootstrap ClassLoader 没有父加载器,它会直接加载核心类库;
  • Extension ClassLoader 将委托给 Bootstrap ClassLoader;
  • Application ClassLoader 将委托给 Extension ClassLoader。

Android中的类加载器

  在Android中,类加载器的工作方式与Java标准中的类加载机制类似,但为了适应移动环境的特殊需求,Android对类加载器进行了优化和扩展。Android的类加载器主要用于加载 .dex 文件(Dalvik/ART Executable)而非标准的 .class 文件,这些 .dex 文件是经过优化的,适用于Android的Dalvik虚拟机和ART运行时环境。

  • BootClassLoader(引导类加载器)
      和Java中的Bootstrap ClassLoader类似,负责加载Android核心框架和库文件中的类,如Java标准库和Android基础框架的类。由系统提供,确保这些类优先加载。BootClassLoader 是内置的,不暴露给开发者,也无法直接访问。

  • PathClassLoader(路径类加载器)
      PathClassLoader 用于加载应用程序的代码和资源,即开发者自己的代码和依赖库(如 .dex 文件和 .apk 文件)。其加载路径是应用的安装路径和 libs 目录,通常用于加载应用的主类和标准库,不支持加载外部的 .jar 文件。
      PathClassLoader 可以在应用的 ClassLoader 层次结构中被直接访问,并通过 getClassLoader() 方法获取。

  • DexClassLoader
      DexClassLoader 是Android特有的类加载器,允许在运行时加载动态 .dex 文件或 .jar 文件。这在Java标准中没有对应的类加载器。可以加载存储在外部存储中的 .dex 或 .jar 文件,常用于插件化开发和动态加载第三方库。DexClassLoader 可以灵活加载动态 .dex 文件,这在应用需要动态更新或加载新功能模块时非常实用。

Android中的双亲委派

  Android的类加载器仍然采用双亲委派模型,确保系统类优先加载,避免与应用类发生冲突。这意味着 BootClassLoader 会先加载系统库和框架类,只有当上层类加载器无法加载某个类时,才会尝试由应用的 PathClassLoaderDexClassLoader 来加载。
  Android将 .class 文件转化为 .dex 文件,以优化内存使用和执行效率。 .dex 文件可以包含多个类,并且经过压缩和优化,便于在移动设备上的高效加载。Android的 DexClassLoaderPathClassLoader 都能将 .dex 文件加载到内存中。

Android中查看特定类的类加载器信息

  新建一个Native C++项目,在onCreate方法里添加代码

        ClassLoader thisclassloader = MainActivity.class.getClassLoader();
        ClassLoader stringclassloader = String.class.getClassLoader();
        Log.d("classloader","MainActivity is in "+thisclassloader.toString());
        Log.d("classloader","String is in "+stringclassloader.toString());

在这里插入图片描述
可以看到打印出来的一些信息

MainActivity is in dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.example.classloader01-Kjp6QH8dtZClZJTtM2hgBg==/base.apk"],nativeLibraryDirectories=[/data/app/com.example.classloader01-Kjp6QH8dtZClZJTtM2hgBg==/lib/arm64, /data/app/com.example.classloader01-Kjp6QH8dtZClZJTtM2hgBg==/base.apk!/lib/arm64-v8a, /system/lib64]]]

String is in java.lang.BootClassLoader@16fc266

  MainActivityPathClassLoader 加载,这是Android应用中常见的类加载器,主要用于加载应用的 .dex 文件和库。PathClassLoader 的描述中包含了zip file 路径:这是 base.apk 的文件路径,即应用的主要 APK 文件;nativeLibraryDirectories 路径:这是包含本地库(如 .so 文件)的目录,在 64 位设备上包含 /lib/arm64 和 /lib/arm64-v8a,以及系统目录 /system/lib64。
  String 类是由 BootClassLoader 加载的,日志中显示了其为 java.lang.BootClassLoader@16fc266。在Android中,BootClassLoader 是用来加载核心Java类库(如 java.lang.、java.util. 等)。

分析源码

  • ClassLoader
      在源码中找到ClassLoader类,发现它是一个抽象类在这里插入图片描述
  • PathClassLoader
      在源码中查找PathClassLoader类,发现它是在dalvik.system包下的类,继承自BaseDexClassLoader
    package dalvik.system;
    public class PathClassLoader extends BaseDexClassLoader {
        public PathClassLoader(String dexPath, ClassLoader parent) {
            super(dexPath, null, null, parent);
        }
        public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent) {
            super(dexPath, null, librarySearchPath, parent);
        }
    }
    
  • BaseDexClassLoader
    去源码中找BaseDexClassLoader类,发现它也是在dalvik.system包下的类,继承自ClassLoader
    在这里插入图片描述
  • parent属性
    parent属性是ClassLoader类的属性,它的类型是ClassLoader,它就是父加载器了
    在这里插入图片描述
  • getParent()方法
    getParent方法是ClassLoader类的类方法,用于获取当前类加载器的父类加载器。
    在这里插入图片描述

写一个函数用来打印父加载器

    public static void printClassLoader(ClassLoader classLoader){
        Log.e("printClassLoader ","this->"+classLoader.toString());
        ClassLoader parent = classLoader.getParent();
        if (parent==null){
            Log.e("printClassLoader","parent is null");
        } else {
            while (parent!=null){
                Log.e("printparentClassLoader ","parent->"+parent.toString());
                parent=parent.getParent();
                if (parent==null){
                    Log.e("printClassLoader","parent is null");
                }
            }
        }
    }
  • MainActivity类的ClassLoader作为参数传进去
    printClassLoader(thisclassloader);在这里插入图片描述
    根据下面结果可以知道PathClassLoader的父加载器是BootClassLoaderBootClassLoader的父类加载器是null

    this->dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.example.classloadertest-epsw2WZfB2ncDxfrINyUmg==/base.apk"],nativeLibraryDirectories=[/data/app/com.example.classloadertest-epsw2WZfB2ncDxfrINyUmg==/lib/arm64, /data/app/com.example.classloadertest-epsw2WZfB2ncDxfrINyUmg==/base.apk!/lib/arm64-v8a, /system/lib64]]]
    parent->java.lang.BootClassLoader@84d413c
    parent is null
    
  • String类的ClassLoader作为参数传进去
    printClassLoader(stringclassloader);
    在这里插入图片描述
    BootClassLoader的父类加载器是null

使用类加载器加载类

  • 使用thisclassloader加载MainActivity

            try {
                Class MainActivityClass = thisclassloader.loadClass("com.example.classloadertest.MainActivity");
                Log.e("classloadertest","load MainActivity success"+MainActivityClass.toString());
            } catch (ClassNotFoundException e) {
                throw new RuntimeException(e);
            }
    

    thisclassloaderPathClassLoader加载器,它可以加载MainActivity
    在这里插入图片描述

  • 使用stringclassloader加载MainActivity

            try {
                Class MainActivityClass = stringclassloader.loadClass("com.example.classloadertest.MainActivity");
                Log.e("classloadertest","load MainActivity success!"+MainActivityClass.toString());
            } catch (ClassNotFoundException e) {
                throw new RuntimeException(e);
            }
    

    stringclassloaderBootClassLoader加载器,无法加载MainActivity,会触发异常
    在这里插入图片描述

  • 使用thisclassloader加载String

            try {
                Class StringClass = thisclassloader.loadClass("java.lang.String");
                Log.e("classloadertest","load java.lang.String success!"+StringClass.toString());
            } catch (ClassNotFoundException e) {
                throw new RuntimeException(e);
            }
    

    thisclassloaderPathClassLoader加载器,它的父加载器是BootClassLoader加载器,可以加载String类 。在这里插入图片描述

  • 使用stringclassloader加载String

            try {
                Class StringClass = stringclassloader.loadClass("java.lang.String");
                Log.e("classloadertest","load java.lang.String success!"+StringClass.toString());
            } catch (ClassNotFoundException e) {
                throw new RuntimeException(e);
            }
    

    stringclassloaderBootClassLoader加载器,可以加载String类。
    在这里插入图片描述

### Java类加载机制 Java虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这个过程就是虚拟机的类加载机制。Java是先编译后执行的语言,类型的加载、连接和初始化在运行期间执行,依赖于动态连接和动态加载的特点,让Java应用具有极高扩展性和灵活性[^1]。 ### 类加载生命周期 类在虚拟机中的生命周期包括加载(Loading)、验证(Verification)、连接(Linking)、初始化(Initialization)、使用(Using)、卸载(Unloading)。其中,验证、准备、解析3个部分称为连接。加载、验证、准备、初始化、卸载这5个阶段的顺序是确定的,类的加载过程必须按照这种顺序按部就班地开始,不过这些阶段是互相交叉的混合式进行,通常会在一个阶段执行过程中调用、激活下一个阶段。而解析阶段则不一定,它可能会因为支持动态绑定而在初始化之后[^2]。 ### 加载阶段核心动作 加载过程是把class字节码文件载入到虚拟机中,至于从哪儿加载,虚拟机设计者并没有限定,可以从文件、压缩包、网络、数据库等地方加载class字节码。加载阶段的核心动作包括:通过一个类的全限定名来获取其定义的二进制字节流;将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构;在Java堆中生成一个代表这个类的`java.lang.Class`对象,作为对方法区中这些数据的访问入口[^3]。 ### 连接阶段关键流程 连接阶段包含验证、准备和解析三个步骤: - **验证**:作为连接阶段的第一步,其目的是确保被加载的类的正确性[^3]。 - **准备**:为类变量分配内存并设置类变量初始值,这些变量所使用的内存都将在方法区中进行分配。 - **解析**:虚拟机将常量池内的符号引用替换为直接引用,解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符7类符号引用进行。 ### 初始化阶段核心机制 初始化阶段是类加载过程的最后一步,在这个阶段,才真正开始执行类中定义的Java程序代码(或者说是字节码)。初始化阶段是执行类构造器`<clinit>()`方法的过程。`<clinit>()`方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块(`static{}`块)中的语句合并产生的,编译器收集的顺序是由语句在源文件中出现的顺序所决定的。 ### 类加载器双亲委派机制 类加载器负责加载class文件,将字节码加载到内存中形成Class对象。类加载器之间存在层次关系,这种层次关系称为类加载器双亲委派模型。该模型在JDK1.2期间被引入并广泛应用于之后几乎所有的Java程序中,但它并不是一个强制性的约束模型,而是Java设计者们推荐给开发者的一种类的加载器实现方式。 双亲委派模型的工作流程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把请求委托给父加载器去完成,依次向上,因此,所有的类加载请求最终都应该被传递到顶层的启动类加载器中,只有当父加载器在它的搜索范围中没有找到所需的类时,即无法完成该加载,子加载器才会尝试自己去加载该类。使用双亲委派模型来组织类加载器之间的关系,好处是Java类随着它的类加载器(所在的目录)一起具备了一种带有优先级的层次关系,这对于保证Java程序的稳定运作很重要。例如,类`java.lang.Object`类存放在JDK\jre\lib下的rt.jar之中,因此无论是哪个类加载器要加载此类,最终都会委派给启动类加载器进行加载,保证了`Object`类在程序中的各种类加载器中都是同一个类[^4]。 ```java // 简单示例,展示类加载器的使用 public class ClassLoaderExample { public static void main(String[] args) { ClassLoader classLoader = ClassLoaderExample.class.getClassLoader(); System.out.println("当前类的类加载器: " + classLoader); System.out.println("父类加载器: " + classLoader.getParent()); System.out.println("祖父类加载器: " + classLoader.getParent().getParent()); } } ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值