一、ClassLoader概述
ClassLoader(类加载器)是Java虚拟机的一项核心技术,实现了虚拟机动态加载类及其依赖的特性。ClassLoader主要负责查找并加载类文件,将其转换成Java类,在需要的时候初始化类,并为Java程序提供必要的运行时环境。在Android系统中,ClassLoader也占据了同样的核心位置。
Android应用程序运行在DVM(Dalvik虚拟机)或ART(Android Runtime)上,这两个运行时环境使用的ClassLoader也不同。DVM使用的ClassLoader是PathClassLoader,它是一种基于Dex文件的类加载器。ART使用的ClassLoader是ArtClassLoader,它是一种基于Oat文件的类加载器。以下我们将以PathClassLoader为例,详细介绍Android ClassLoader机制的实现原理。
二、ClassLoader分类
ClassLoader的实现机制不一,根据具体的实现方式,可以将ClassLoader分为以下几种类型:
1. BootStrap ClassLoader
负责加载JVM运行需要的基础类库(如rt.jar、i18n.jar、sunrsasign.jar等)。BootStrap ClassLoader是JVM内置的ClassLoader,并不继承自java.lang.ClassLoader,它是用C++编写的,提供了JVM的基础服务。
2. Extension ClassLoader
负责加载JRE扩展的类库(如jce.jar、localedata.jar等)。Extension ClassLoader是由Sun的扩展类加载器实现的,它继承自ClassLoader,它的父类加载器是BootStrap ClassLoader。
3. Application ClassLoader
也称作System ClassLoader,是ClassLoader的默认实现。它负责加载CLASSPATH路径下的类文件和应用程序本身的类文件。Application ClassLoader是Java应用程序开发中,开发人员最关心的ClassLoader实现之一。
另外的还有自定义ClassLoader,它可以继承ClassLoader并重新实现加载机制。开发人员可以使用自定义ClassLoader加载特定的类文件或按照特定规则实现类的加载,从而实现一些特定的功能。
三、ClassLoader实现原理
ClassLoader机制的实现依赖于ClassLoader的双亲委派机制。在ClassLoader加载类的时候,首先询问它的父类加载器是否能够加载该类,如果不能,则自身尝试加载。如果自身不能加载,则逐级向上委派,直至BootStrap ClassLoader都不能加载,最终由自身去完成类的加载。这种方式保证了一个类在虚拟机中只有一个版本(不同的ClassLoader实例会加载不同的版本,无法互相访问),避免了类的重复加载,也保证了类的安全性。
ClassLoader机制的实现过程大致可以分为以下几个步骤:
1. 对于需要加载的类,首先从缓存中查找是否已经加载过。
ClassLoader会将已经加载成功的类文件缓存在内存中,以便下次使用时可以直接从缓存中获取。如果已经存在,则返回成功加载的类对象。
2. 如果没有找到可用的缓存,则请求父ClassLoader进行加载。
ClassLoader在委托父ClassLoader之前,先使用findLoadedClass()方法再进行一次查找,以避免出现重复加载的现象。如果父ClassLoader存在,则请求父ClassLoader进行加载。如果父ClassLoader也无法加载,则继续向上委派,直至委派到BootStrap ClassLoader。
3. 如果所有的父ClassLoader都无法加载,则从本地文件系统或者网络等位置获取要加载的.class文件。
ClassLoader在查找.class文件的时候,会根据一定规则查找指定路径下的.class文件。一般包括CLASSPATH路径、系统默认路径以及其他用户自定义的路径。如果能够查找到要加载的.class文件,则将它转换成Java类并返回。否则,抛出ClassNotFoundException。
4. 将加载成功的类文件存储到缓存中。
为了以后可以重用已经成功加载的类文件,ClassLoader会将成功加载的类文件存储到缓存中,下次再使用时可以直接从缓存中获取。
四、Class对象与ClassLoader
每个Java类在虚拟机中都有一个对应的Class对象,ClassLoader负责将类文件加载到虚拟机中,并转换成Class对象。通过Class对象,可以获取类的信息,包括类名、父类、接口、字段、方法等信息。ClassLoader不仅加载类文件,它还负责创建和管理Class对象,即ClassLoader与Class对象是一一对应的关系。ClassLoader利用类的全限定名来查找并加载类,因此不同的ClassLoader实例可以加载同名的类,但这些类之间是互不共享的,可以看成具有相同类名但具有不同命名空间的不同类。
以下是一个简单的例子,介绍ClassLoader和Class对象的关系:
public class Main { public static void main(String[] args) throws ClassNotFoundException { // 获取系统ClassLoader ClassLoader cl = ClassLoader.getSystemClassLoader(); // 加载Hello类 Class c = cl.loadClass("com.example.Hello"); // 输出Hello的类名 System.out.println("类名:" + c.getName()); // 输出Hello的Class对象的ClassLoader System.out.println("ClassLoader:" + c.getClassLoader()); } }
以上代码中,使用ClassLoader.getSystemClassLoader()方法获取系统ClassLoader,然后使用ClassLoader加载指定的类。最后输出类的名称和ClassLoader。在Java中,类的名称是唯一的,而ClassLoader是有继承链的。因此在ClassLoader的继承链上,可以有多个ClassLoader实例同时加载同名的类。另外,需要注意,系统ClassLoader是默认的ClassLoader,如果没有指定ClassLoader,一般都会使用系统ClassLoader。
五、注意事项
ClassLoader机制对程序的运行维护起到了重要的作用,但是在使用时还需要注意以下事项:
1. 类的可访问性
当ClassLoader加载一个类时,要保证该类的访问性。如果类文件的可访问性限制了访问该类的ClassLoader的话,则会抛出SecurityException异常。
2. 类的初始化时机
一个类只有被使用时才会被初始化。类的初始化是指虚拟机为该类生成Class对象,并且在内存中分配资源。类的初始化时机包括:创建该类的实例对象、访问类的静态成员(被final修饰的静态成员除外)、使用是客户端JVM所在的Java程序启动时被标明为启动类的类、使用了反射API的类等。如果一个类没有被使用,则不会被加载和初始化,即便ClassLoader已经加载了该类的字节码文件。
3. 线程上下文ClassLoader
线程上下文ClassLoader用于在多个线程中对ClassLoader进行传递和继承。线程的上下文ClassLoader可以通过Thread类的setContextClassLoader()方法设置。当一个线程需要加载一个类时,它首先尝试使用自己的ClassLoader,如果未找到,则使用其线程上下文ClassLoader。如果该线程上下文ClassLoader也未找到,则委托父线程的上下文ClassLoader进行查找,直至BootStrap ClassLoader。线程上下文ClassLoader基本上是由应用程序自行负责管理的,JVM在不同的线程中使用不同的ClassLoader对同一类进行加载时,可能会导致出现问题。因此,在引入第三方类库、动态加载类和OSGi等场景下,都需要注意ClassLoader的使用。
六、总结
ClassLoader是Java虚拟机的基础服务,它提供了Java程序动态加载类的能力。Android应用程序同样运用了ClassLoader机制,用于实现在运行时动态加载和卸载类。ClassLoader加载类的过程遵循双亲委派机制,保证了类在虚拟机中的唯一性和安全性。ClassLoader机制是Java和Android程序设计不可或缺的一部分,熟练掌握ClassLoader的使用,对程序的设计和性能优化有很大的帮助。