Android---深入理解ClassLoader的加载机制

目录

Java 中的 ClassLoader

1. APPClassLoader 系统类加载器

2. ExtClassLoader 扩展类加载器

3. BootstrapClassLoader 启动类加载器

双亲委派模式(Parents Delegation Model)

Android 中的 ClassLoader

1. PathClassLoader

2. DexClassLoader

总结


一个完整的 Java 程序是由多个 .class 文件组成的,在程序运行的过程中,需要将这些 .class 文件加载到 JVM 中才可以使用,而负责加载这些 .class 文件的就是类加载器(ClassLoader)

Java 中的类何时被类加载器加载

Java 程序启动时,并不会一次性加载程序中所有的 .class 文件,而是在程序运行过程中,动态的加载相应的类到内存中。通常情况下,Java 程序中的 .class 文件会在以下两种情况下被 Class Loader 主动加载到内存:

1. 调用类构造器;

2. 调用类中的静态变量或者静态方法。

Java 中的 ClassLoader

JVM 中自带3个类加载器:

\bullet 启动类加载器 BootstrapClassLoader;

\bullet 扩展类加载器 ExtClassLoader(JDK1.9 之后,改名为 PlatformClassLoader);

\bullet 系统类加载器 APPClassLoader。

1. AppClassLoader 系统类加载器

部分源码如下

可以看出 AppClassLoader 主要加载系统属性“java.class.path”配置下类文件,也就是环境变量 classpath 配置的路径下的文件。因此,AppClassLoader 是面向用户的类加载器。我们自己编写的代码以及使用的第三方 jar 包,通常都是由它来加载的。

2. ExtClassLoader 扩展类加载器

部分源码如下:

可以看出,扩展类加载器(ExtClassLoader )主要加载系统属性“java.ext.dirs”配置下类文件,可以通过如下代码打印出这个属性来查看具体有哪些文件。

System.out.println(System.getProperty("java.ext.dirs"));

3. BootstrapClassLoader 启动类加载器

BootstrapClassLoader 是由 C/C++ 语言编写的,它本身属于虚拟机的一部分,因此无法在 java 代码中直接获取它的引用。如果尝试在 Java 层获取 BootstrapClassLoader 的引用,系统会返回 null。

启动类加载器加载“sun.boot.class.path”配置下类文件,可以打印这个属性来查看具体有哪些文件

System.out.println(System.getProperty("sun.boot.class.path"));

结果如下:

可以看出这些全身 jre 目录下的的 jar 包或者 classes 文件。

双亲委派模式(Parents Delegation Model)

既然 JVM 中已经有了这3种 ClassLoader,那么 JVM 又是如何知道该使用哪一个类加载器去加载相应的类呢?答案这是:双亲委派模式。

双亲委派模式:当类加载器受到加载类或资源的请求时,通常都是先委托给父类加载器加载,只有当父类加载器找不到指定的类或资源时,自身才会执行实际的类加载过程。

其具体实现代码是在CLassLoader.java 中的 loadClass 方法中,如上图所示:

解释说明:上图中1处判断该 class 是否已经加载。如果已经加载,直接将该 class 返回;2处,如果该 class 没有被加载过,则判断 parent 是否为空,如果不为空则将加载的任务委托给 parent;3处,如果 parent 为空,则直接调用 BootstrapClass Loader 加载该类;4处,如果 parent 和 BootstrapClassLoader 都没有加载成功,则调用当前 ClassLoader 的 findClass() 方法继续尝试加载。

那么这个 parent 是什么呢?我们可以看 ClassLoader 的构造器,如下:

可以看出,在每一额 ClassLoader 中都有一个 ClassLoader 类型的 parent 引用,并且在构造器中传入值。继续查看源码,可以看到 AppClassLoader 传入的 parent 就是 ExtClassLoadr,而 ExtClassLoadr并没有传入任何 parent,也就是 null。

举例说明:

Test test = new Test();

默认情况下,JVM 首先调用 AppClassLoader 去加载 Test 类

1. APPClassLoader 将加载任务委派给它的父类加载器(parent)--ExtClassLoader;

2. ExtClassLoader 的 parent 为 null,则直接调用 BootstrapClassLoader 加载该类;

3. BootstrapClassLoader 在 jdk/jre 目录下无法找到 Test 类,因此返回的 Class 为 null;

4. 因为 parent 和 BootstrapClassLoader 都没能成功加载 Test 类,所以 AppClassLoader 会调用自身的 findClass() 方法来加载 Test 类。

最终,Test 就是被系统类加载器(AppClassLoader)加载到内存中的。

注意:“双亲委派”机制只是 Java 推荐的机制,并不是强制的机制。可以继承 java.lang.ClassLoader 类,实现自己的类加载器。如果想保持双亲委派模型,应该重写 findClass(name) 方法;如果想破坏双亲委派模型,可以重写 loadClass(name)方法。

自定义 ClassLoader

JVM 中预置的3种 ClassLoader 只能加载特定目录下的 .class 文件。如果想加载其它特殊位置下的 jar 包或类时(如网络或磁盘上),默认的 ClassLoader 就不能满足我们的需求。所以需要定义自己的 ClassLoader 来加载特定目录下的 .class 文件。

自定义 ClassLoader 步骤

1. 自定义一个类(MyClassLoader)继承抽象类 ClassLoader;

2. 重写 findClass 方法;

3. 在 findClass 方法中,调用 defineClass() 方法将字节码转换成 Class 对象,并返回。

自定义 ClassLoder 实践

1. 创建一个测试类 Secret.java,实现简单的打印功能。该文件存放在 D:\HL\ 目录下。在终端通过 javac 命令生成 Secret.class 文件。

public class Secret {
	public void printSecret() {
		System.out.println("I am a girl!");
	}
}

2. 创建 MyClassLoade 继承自 ClassLoader,重写 findClass() 方法。

public class MyClassLoader extends ClassLoader{
	
	private String filePath;
	
	public MyClassLoader(String path) {
		filePath = path;
	}
	
	@Override
	protected Class<?> findClass(String name){
		// newPath = "D:\HL\Secret.class"
		// File.separator 来构建文件路径,以确保在不同操作系统上都能正常工作
		String newPath = filePath + File.separator + name.replace('.', File.separatorChar) + ".class";
		byte[] classBytes = null;
		
		try {
			Path path = Paths.get(newPath);
			classBytes = Files.readAllBytes(path);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		// 调用 defineClass 创建 class 并返回
		return defineClass(name, classBytes, 0, classBytes.length);
	}

}

3. 测试

public class TestMyClassLoader {

	public static void main(String[] args) {
		// 创建自定义 ClassLoader 对象 
		// "D:/HL/" : 需要动态加载的 class 的路径
		MyClassLoader myClassLoader = new MyClassLoader("D:/HL/");
		try {
			//"software_test.Secret" 需要动态加载的类名
			Class c = myClassLoader.loadClass("software_test.Secret");
			if(c != null) {
				Object obj = c.newInstance();
				// 通过反射调用 Secret 的 printSecret 方法,即需要动态调用的方法名称
				Method method = c.getDeclaredMethod("printSecret", null);
				method.invoke(obj, null);
			}
					
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

}

Android 中的 ClassLoader

本质上 Android 和传统的 JVM 是一样的,也需要 ClassLoader 将目标类加载到内存,类加载器之间也复合双亲委派模型。但是在 Android 中,ClassLoader的加载细节有略微的差别。在 Android 虚拟机里是无法直接运行 .class 文件,Android 中会将所有的 .class 文件转化为一个 .dex 文件。Android 将加载 .dex 文件的实现封装在 BaseDexClassLoader 中,一般我们使用它的两个子类:PathClassLoader 和 DexClassLoader。

1. PathClassLoader

用来加载系统 apk 和被安装到手机中 apk 内的 dex 文件。它的两个构造函数如下:

参数说明:

dexPath:dex 文件路径,或者包含 dex 文件的 jar 包路径;

librarySearchpath:C/C++ native 库路径。

PathClassLoader 代码里除了这两个构造函数外就没有其它代码了。具体的实现都是在 BaseDexClassLoader 里面。

当一个 app 被安装到手机后,apk 里面的 class.dex 中的 .class 均是通过 PathClassLoader 来加载的。

2. DexClassLoader

对比 PathClassLoader 只能加载已经安装的应用的 dex 或 apk 文件,DexClassLoader 则没有此限制,可以从 SD 卡上加载包含 class.dex 的 jar 包或者是 apk 文件。这也是插件化或热修复的基础,在不需要安装应用的情况下完成需要使用的 dex 文件的加载。

DexClassLoader 的源码里只有一个构造函数,如下:

参数说明:

dexPath:包含 class.dex 的 apk,jar 文件路径,多个路径使用文件分割符(默认是“:”)分隔;

 optimizedDirectory:用来缓存优化的 dex 文件的路径,即从 apk 或 jar 文件中提取出来的 dex 文件,该路径不可以为空,且应该是私有的,有读写权限的路径。

总结

\bullet ClassLoader 是用来加载 class 文件的,不管是 jar 中还是 dex 中的 class;

\bullet Java 中的 ClassLoader 通过双亲委托来加载各自指定路径下的 class 文件;

\bullet 可以自定义 ClassLoader,一般覆盖 findClass() 方法,不建议重写 loadClass 方法;

\bullet Android 中常用的两个 ClassLoader 分别为:PathClassLoader 和 DexClassLoader。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: Android Java虚拟机ART是一种全新的虚拟机,它是Android Lollipop操作系统中默认的运行时环境,相比于旧版的Dalvik虚拟机,它能够提供更好的性能和体验。 ART的最大特点是在使用前将字节码转换为机器码,这样可以在运行时减少解释和编译的时间,从而提高应用程序的响应速度。此外,在ART中也引入了一些新的技术,例如预编译、AOT和热编译等,这些都能够优化应用程序的启动速度和运行效率。 在深入研究ART之前,必须先了解Java虚拟机(JVM)的基本概念和原理。JVM是一种运行Java程序的虚拟机,将Java源代码转换为字节码,再由虚拟机解释执行。同样地,ART也采用相同的原理来实现应用程序的运行,只不过它将字节码转换为机器码,从而提高了运行速度和效率。 因此,熟悉Java虚拟机和ART的工作原理,能够帮助开发者更好地理解和优化应用程序的性能。此外,对于一些需要高效运行的应用场景(例如游戏、图像处理等),ART也能够提供更好的运行环境,提高应用程序的稳定性和响应能力。 总之,深入理解Android Java虚拟机ART对于开发者来说非常重要,尤其是在需要优化应用程序性能和响应速度的情况下。只有深入了解ART的原理和特点,才能更好地应用它来提高应用程序的运行效率。 ### 回答2: Android Java虚拟机ART是Android系统中最新的运行时环境。相较于旧有的Dalvik虚拟机,ART采用预编译技术,将应用程序字节码一次性编译为本地机器码,提高了应用程序的运行效率和响应速度,同时也降低了资源消耗。因此,深入理解Android Java虚拟机ART对于Android开发者来说是非常必要的。 深入学习ART,我们需要了解其内部运作机制,包括Dex编译、ClassLoader、Garbage Collection等关键概念。ART采用了AOT和JIT两种编译方式,也采用了一些新的优化方法,如Profile Guided Optimization(PGO)、Image Space Estimation等,以提高应用程序的可执行性和启动时间。 ART的ClassLoader实现了一种高效的动态加载技术,它使得应用程序可以在运行时动态更新代码库、插件包等,从而大大扩展了应用程序的功能和灵活性。同时ART的ClassLoader也是构建Android虚拟化环境的基础,它可以从不同的应用程序中加载开放的类,并为每个应用程序提供一个独立的执行环境。 最后,ART的Garbage Collection机制实现了一种全新的分代收集算法,将耗费大量时间的垃圾回收操作分散到不同的虚拟机堆内,从而大幅度提高了应用程序的性能和响应速度。 总之,深入理解Android Java虚拟机ART对于Android开发者来说十分关键,它将为我们提供更为深入的开发思路和方法,使我们的应用程序更加高效,同时也为我们的Android应用程序开发添上浓墨重彩的一笔。 ### 回答3: Android Java虚拟机ART (Android Runtime)是安卓4.4系统及以上版本中的默认虚拟机。相比原先的Dalvik虚拟机,ART可实现更高的性能和更好的系统稳定性。 ART的核心思想是AOT( Ahead of Time)编译。它在应用程序安装的时候就将应用程序代码转换成本地机器指令并编译成机器代码,以C/C++库的形式保存在设备上。相比Dalvik,在应用程序的执行过程中省去了JIT编译的时间和运算,能够提高应用程序的运行速度。 除此之外,ART还有几个重要的特点: 1. 超低功耗:ART的AOT编译技术使得应用执行时可以直接使用本地机器指令,减少了CPU的时间浪费,使得应用程序的功耗更低。 2. 内存占用减少: ART允许应用程序在运行时进行类加载,实现更高效的内存管理。相比Dalvik虚拟机,ART在处理内存和垃圾回收时能够更好地利用系统资源,减少了应用程序所占用的内存。 3. 支持快速应用开发:通过使用ART虚拟机可以通过模块形式快速开发出具有更好体验的应用程序。 总之,深入理解Android Java虚拟机ART需要着重理解ART AOT编译原理、内存管理机制、以及对快速应用开发的支持。这些特点的综合优势使得安卓应用程序能够实现更快的运行速度、更低的功耗、更快的开发效率和更好的用户体验。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

别偷我的猪_09

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

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

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

打赏作者

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

抵扣说明:

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

余额充值