类加载器
在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
会先加载系统库和框架类,只有当上层类加载器无法加载某个类时,才会尝试由应用的 PathClassLoader
或 DexClassLoader
来加载。
Android将 .class 文件转化为 .dex 文件,以优化内存使用和执行效率。 .dex 文件可以包含多个类,并且经过压缩和优化,便于在移动设备上的高效加载。Android的 DexClassLoader
和 PathClassLoader
都能将 .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
MainActivity
被 PathClassLoader
加载,这是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
的父加载器是BootClassLoader
,BootClassLoader
的父类加载器是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); }
thisclassloader
是PathClassLoader
加载器,它可以加载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); }
stringclassloader
是BootClassLoader
加载器,无法加载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); }
thisclassloader
是PathClassLoader
加载器,它的父加载器是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); }
stringclassloader
是BootClassLoader
加载器,可以加载String
类。