Java和Android的类加载器和类加载机制详解

Java的Class(类)加载器,主要工作在Class加载的“加载阶段”,其主要作用是从系统外部获取Class的二进制数据流。


1 类加载机制

类加载机制主要有如下3种:

  1. 全盘负责:所谓全盘负责,就是当一个类加载器负责加载某个Class时,该Class所依赖和引用其他Class也将由该类加载器负责载入,除非显示使用另外一个类加载器来载入。
  2. 双亲委派:所谓的双亲委派,则是先让父类加载器试图加载该Class,只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类。通俗的讲,就是某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父加载器,依次递归,如果父加载器可以完成类加载任务,就成功返回;只有父加载器无法完成此加载任务时,才自己去加载。
  3. 缓存机制:缓存机制将会保证所有加载过的Class都会被缓存,当程序中需要使用某个Class时,类加载器先从缓存区中搜寻该Class,只有当缓存区中不存在该Class对象时,系统才会读取该类对应的二进制数据,并将其转换成Class对象,存入缓冲区中。这就是为很么修改了Class后,必须重新启动JVM,程序所做的修改才会生效的原因。

Java的JVM在加载类时默认采用的是双亲委派机制和缓存机制。

2 类与类加载器

对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立其在 Java 虚拟机的唯一性。每个类加载器都拥有一个独立的类名称空间。也就是说:比较两个类是否「相等」,只要在这两个类是由同一个类加载器加载的前提下才有意义,否则,即使这两个类来源于同一个 Class 文件,被同一个虚拟机加载,只要加载它们的类加载器不同,那这两个类就必定不相等。

这里所指的"相等",包括代表类的Class对象的equals()方法、isAssignableFrom()方法、islnstance()方法的返回结果,也包括使用instanceof关键字做对象所属关系判定等情况。如果没有注意到类加载器的影响,在某些情况下可能会产生具有迷惑性的结果,下面代码演示了不同的类加载器对instanceof关键字运算的结果的影响。

如下案例:

public class ClassLoaderTest {
    public static void main(String[] args) throws Exception {
        //自定义类加载器
        ClassLoader myLoader = new ClassLoader() {
            @Override
            public Class<?> loadClass(String name)
                    throws ClassNotFoundException {
                try {
                    String fileName = name.substring(name.lastIndexOf(".") + 1)
                            + ".class";
                    InputStream is = getClass().getResourceAsStream(fileName);
                    if (is == null) {
                        return super.loadClass(name);
                    }
                    byte[] b = new byte[is.available()];
                    is.read(b);
                    return defineClass(name, b, 0, b.length);
                } catch (IOException e) {
                    throw new ClassNotFoundException(name);
                }
            }
        };
        //使用自定义类加载器来加载类ClassLoaderTest
        Class newClass = myLoader.loadClass("com.ikang.JVM.classloader.ClassLoaderTest");
        //反射获取实例
        Object obj1 = newClass.newInstance();
        //获取实例所属的类全路径名
        System.out.println(obj1.getClass());


        //原生的类加载器来加载类ClassLoaderTest
        Class<?> oldClass = ClassLoaderTest.class.getClassLoader().loadClass("com.ikang.JVM.classloader.ClassLoaderTest");
        //反射获取实例
        Object obj2 = oldClass.newInstance();
        //获取实利所属的类全路径名
        System.out.println(obj2.getClass());


        //直接获取ClassLoaderTest类的类全路径名
        System.out.println(ClassLoaderTest.class);


        /*从上面的上个输出中,看起来它们属于同一个类,但是实际上并不是,如下判断*/
        System.out.println(obj1 instanceof ClassLoaderTest);
        System.out.println(obj2 instanceof ClassLoaderTest);
    }
}


3 类加载器种类

3.1 虚拟机规范的角度

根据《Java虚拟机规范 javaSE8》,Java虚拟机支持两种不同的类加载器:

  1. 引导类加载器(Bootstrap ClassLoader):它使用C++实现(这里仅限于HotSpot,也就是JDK1.5之后默认的虚拟机,有很多其他的虚拟机是用Java语言实现的),是虚拟机自身的一部分。
  2. 所有其他的类加载器:这些类加载器都由Java语言实现,独立于虚拟机之外,并且全部继承自抽象类java.lang.ClassLoader,这些类加载器需要由引导类加载器加载到内存中之后才能去加载其他的类。
 Java中的类加载器主要有两种类型,分别是系统类加载器和自定义类加载器。其中系统类加载器包括3中,分别是 Bootstrap ClassLoader、Extensions ClassLoader和Application ClassLoader。

3.2 开发人员的角度

站在Java开发人员的角度来看,类加载器可以大致划分为以下三类:

3.2.1 启动类加载器

Bootstrap ClassLoader,也称根类加载器/引导类加载器。它是在Java虚拟机启动后初始化的,用来加载 Java 的核心类库, 例如< JAVA_HOME >/jre/lib/rt.jar里的所有class,比如java.time.、java.util.、java.nio.、java.lang.、java.text.、java.sql.、java.math、JUC包等各种自带的类库。

启动类加载器无法被Java程序直接引用,用户在编写自定义类加载器时,如果需要把加载请求委派给启动类加载器,那直接使用null代替即可。

下面程序可以获得启动加载器所加载的核心类库,并会看到本机安装的Java环境变量指定的JDK中提供的核心jar包路径:

URL[] urls = Launcher.getBootstrapClassPath().getURLs();
Arrays.stream(urls).forEach(System.out::println);
/*for (URL url : urls) {
    System.out.println(url.toExternalForm());
}*/

本人机器输出如下:

file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/resources.jar
file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/rt.jar
file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/sunrsasign.jar
file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/jsse.jar
file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/jce.jar
file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/charsets.jar
file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/jfr.jar
file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/classes

3.2.2 扩展类加载器

Extension ClassLoader,它用来加载 Java 的扩展库。该加载器由sun.misc.Launcher$ExtClassLoader实现,它负责加载< JAVA_HOME >\jre\lib\ext目录中,或者由java.ext.dirs系统变量指定的路径中的所有类库(如javax.*开头的类),开发者可以直接使用扩展类加载器。它的父加载器为null,并且是使用java语言实现的。

获取扩展类加载器加载的jar包:

public static void main(String[] args) {
    File[] dirs = getExtClassLoaderFile();
    //递归展示jar包
    for (File dir : dirs) {
        showFile(dir.getPath());
    }
}

/**
 * ExtClassLoader类中获取路径的代码
 * 加载<JAVA_HOME>/lib/ext目录中的类库目录
 */
static File[] getExtClassLoaderFile() {
    String s = System.getProperty("java.ext.dirs");
    File[] dirs;
    if (s != null) {
        StringTokenizer st =
                new StringTokenizer(s, File.pathSeparator);
        int count = st.countTokens();
        dirs = new File[count];
        for (int i = 0; i < count; i++) {
            dirs[i] = new File(st.nextToken());
        }
    } else {
        dirs = new File[0];
    }
    return dirs;
}

/**
 * 递归展示jar包
 */
static void showFile(String path) {
    File file = new File(path);
    if (file.exists()) {
        if (file.isFile()) {
            if (file.getName().endsWith(".jar")) {
                System.out.println(file.getAbsolutePath());
                return;
            }
        }
        File[] files = file.listFiles();
        for (File file1 : files) {
            if (file1.isFile()) {
                if (file1.getName().endsWith(".jar")) {
                    System.out.println(file1.getAbsolutePath());
                }
            } else {
                showFile(file1.getPath());
            }
        }
    }
}

3.2.3 应用类加载器

Application ClassLoader,也称系统类加载器。这个类加载器由 sun.misc.Launcher$AppClassLoader 实现。

这个类加载器是 ClassLoader 中的getSystemClassLoader() 方法的返回值,所以一般称它为系统类加载器。它负责加载用户路径(ClassPath)上所指定的类库,开发者可以使用这个类加载器,如果应用程序没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。它的父加载器是 ExtClassLoader。

ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
System.out.println(systemClassLoader);
3.2.4 自定义类加载器

我们的应用程序都是由这3中类加载器互相配合进行加载的,如果有必要,还可以加入自己定义的类加载器。开发人员可以通过继承 java.lang.ClassLoader类,重写findClass()方法,调用defineClass()方法的方式实现自己的类加载器,以满足一些特殊的需求。

为什么会有自定义类加载器?

一方面是由于java代码很容易被反编译,如果需要对自己的代码加密的话,可以对编译后的代码进行加密,然后再通过实现自己的自定义类加载器进行解密,最后再加载。


另一方面也有可能从非标准的来源加载代码,比如从网络来源,那就需要自己实现一个类加载器,从指定源进行加载。

实现自定义 ClassLoader 需要如下两个步骤:

       1)自定义一个类加载器并继承抽象类ClassLoader。

       2)复写findClass方法,并在findClass方法中调用defineClass方法。

       下面我们就自定义一个ClassLoader用来加载位于D:\lib的Class文件。首先编写测试类并生成Class文件,如下所示:

public class Test{
	public void test(){
		System.out.println("I'm the test class on disk D");
	}
}

 将这个Test.java文件放到D:\lib目录中,使用CMD命令进入D:\lib目录中,执行javac Test.java对该java文件进行编译,这时会在 D:\lib 中生成 Test.class文件 。接下来在 Eclipse 中创建一个 Java Project,编写自定义 ClassLoader ,如下所示:

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
 
public class MyClassLoader extends ClassLoader{
	private String path;
 
	public MyClassLoader(String path) {
		this.path = path;
	};
	
	@Override
	protected Class<?> findClass(String name) throws ClassNotFoundException {
		// TODO Auto-generated method stub
		Class<?> clazz = null;
		byte[] classData = loadClassData(name); // ... 1
		if(classData == null) {
			throw new ClassNotFoundException();
		}else {
			clazz = defineClass(name,classData,0,classData.length); // ... 2
		}
		return clazz;
	}
	
	private byte[] loadClassData(String name) {
		String fileName = getFileName(name);
		File file = new File(path,fileName);
		InputStream in = null;
		ByteArrayOutputStream out = null;
		try {
			in = new FileInputStream(file);
			out = new ByteArrayOutputStream();
			byte[] buffer = new byte[1024];
			int length = 0;
			while((length = in.read(buffer)) != -1) {
				out.write(buffer,0,length);
			}
			return out.toByteArray();
		}catch(IOException e) {
			e.printStackTrace();
		}finally {
			try {
				if(in!=null) {
					in.close();
				}
			}catch(IOException e) {
				e.printStackTrace();
			}
			try {	
				if(out!=null) {
					out.close();
				}
			}catch(IOException e) {
				e.printStackTrace();
			}
		}
		
		return null;
	}
	
	
	private String getFileName(String name) {
		int index = name.lastIndexOf(".");
		if(index == -1) {
			return name + ".class";
		}else {
			return name.substring(index + 1) + ".class";
		}
	}
	
}

 注释1处的 loadClassData 方法会获得 class 文件的字节码数组,并在注释2处调用 defineClass 方法将 class 文件的字节码数组转为 Class 类的实例,在loadClassData 方法中要对流进行操作,关闭流的操作要放在 finally 语句块中,并且要对 in 和 out 分别使用try-catch 语句,如果 in 和 out 共同在一个 try- catch 语句中,假设 in.close() 发生异常,就无法执行 out.close()。 最后我们来验证 DMyClassLoader 是否可用,代码如下所示:

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
 
public class ClassLoaderTest {
	public static void main(String[] args) {
		MyClassLoader loader = new MyClassLoader("D:\\lib"); // ... 1
		try {
			Class<?> clazz = loader.loadClass("Test"); // ... 2
			if (clazz != null) {
				Object test = clazz.newInstance();
				System.out.println("test 的类加载器为: " + test.getClass().getClassLoader());
				Method method = clazz.getDeclaredMethod("test", null);
				method.invoke(test,null); // ... 3
			}
		} catch (ClassNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (InstantiationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (NoSuchMethodException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (SecurityException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalArgumentException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

在注释1 处创建 My ClassLoader 并传入要加载类的路径,在注释2 处加载 Class 文件, 需要注意的是,不要在项目工程中存在Test.j ava 的 文件,否则就不会使 MyClassLoader 来加载,而是使用 AppClassLoader 来负责 加载,这样我们定义的 My ClassLoader 就变 得毫 无意义。接下来 在注释3 处通过反射来调用Test 的 test 方法 ,打 印结果如下:

test 的类加载器为: MyClassLoader@4e25154f
I'm the test class on disk D

使用MyClassLoader来加载Class文件,从上面的输出可以看出,Test.class文件成功加载到JVM,并且Test 的 test 方法也正确执行。

4 双亲委派模型

上面这些类加载器之间的关系一般如下图所示:

上图中的类加载器之间的这种层次关系,称为类加载器的双亲委派模型。双亲委派模型要求除了顶层的启动类加载器,其余的类加载器都应该有自己的父类加载器。这里类加载器之间的父子关系一般不会以继承关系来实现,而是使用组合关系来复用父加载器的代码。

双亲委托模型的工作过程是:如果一个类加载器收到了类加载器的请求,它首先不会自己尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父类加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类时,子加载类才会尝试自己去加载)。

4.1 双亲委派机制的优势

采用双亲委派模式的是好处是Java类随着它的类加载器一起具备了一种带有优先级的层次关系,通过这种层级关可以避免类的重复加载,当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次。

其次是考虑到安全因素,java核心api中定义类型不会被随意替换,假设通过网络传递一个名为java.lang.Integer的类,通过双亲委托模式传递到启动类加载器,而启动类加载器在核心Java API发现这个名字的类,发现该类已被加载,并不会重新加载网络传递的过来的java.lang.Integer,而直接返回已加载过的Integer.class,这样便可以防止核心API库被随意篡改。

4.2 双亲委派模型的实现

双亲委派模型对于保证Java 程序的稳定性很重要,但它的实现却非常简单,实现双亲委派的代码都集中在java.lang.ClassLoader 的 loadClass() 方法中:先检查类是否被加载过,若没有则调用父加载器的loadClass() 方法,若父加载器为空则默认使用启动类加载器作为父加载器。如果父加载器失败,抛出 ClassNotFoundException 异常后,再调用自己的 finClass() 方法进行加载。

protected Class<?> loadClass(String name, boolean resolve)
    throws ClassNotFoundException
{
    synchronized (getClassLoadingLock(name)) {
        // 先检查是否被加载过
        Class<?> c = findLoadedClass(name);
        //如果没被加载过
        if (c == null) {
            long t0 = System.nanoTime();
            try {
                //如果父加载器不为null,则首先让父加载器尝试加载该类
                if (parent != null) {
                    c = parent.loadClass(name, false);
                } else {
                    //否则,使用dBootstrapClassloader来加载
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {       
                // ClassNotFoundException thrown if class not found
                // from the non-null parent class loader
            }
            //父类没有加载成功
            if (c == null) {
                // If still not found, then invoke findClass in order
                // to find the class.
                long t1 = System.nanoTime();
                //尝试子类加载器加载
                c = findClass(name);

                // this is the defining class loader; record the stats
                sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                sun.misc.PerfCounter.getFindClasses().increment();
            }
        }
        //根据条件判断是否进行链接操作
        if (resolve) {
            resolveClass(c);
        }
        return c;
    }
}


5 破坏双亲委派模型

双亲委派模型并不是一个强制性的约束模型,而是Java设计者推荐给开发者的类加载器实现方式。在Java的世界中大部分的类加载器都遵循这个模型,但也有例外,到目前为止,双亲委派模型主要出现过3较大规模的“被破坏”情况。

双亲委派模型的第一次“被破坏” 其实发生在双亲委派模型出现之前–即JDK1.2发布之前。由于双亲委派模型是在JDK1.2之后才被引入的,而类加载器和抽象类java.lang.ClassLoader则是JDK1.0时候就已经存在,面对已经存在 的用户自定义类加载器的实现代码,Java设计者引入双亲委派模型时不得不做出一些妥协。为了向前兼容,JDK1.2之后的java.lang.ClassLoader添加了一个新的proceted方法findClass(),在此之前,用户去继承java.lang.ClassLoader的唯一目的就是重写loadClass()方法,因为虚拟在进行类加载的时候会调用加载器的私有方法loadClassInternal(),而这个方法的唯一逻辑就是去调用自己的loadClass()。JDK1.2之后已不再提倡用户再去覆盖loadClass()方法,应当把自己的类加载逻辑写到findClass()方法中,在loadClass()方法的逻辑里,如果父类加载器加载失败,则会调用自己的findClass()方法来完成加载,这样就可以保证新写出来的类加载器是符合双亲委派模型的。

双亲委派模型的第二次“被破坏” 是这个模型自身的缺陷所导致的,双亲委派模型很好地解决了各个类加载器的基础类统一问题(越基础的类由越上层的加载器进行加载),基础类之所以被称为“基础”,是因为它们总是作为被调用代码调用的API。但是,如果基础类又要调用用户的代码,那该怎么办呢。

这并非是不可能的事情,一个典型的例子便是JNDI服务,它的代码由启动类加载器去加载(在JDK1.3时放进rt.jar),但JNDI的目的就是对资源进行集中管理和查找,它需要调用独立厂商实现部部署在应用程序的classpath下的JNDI接口提供者(SPI, Service Provider Interface)的代码,但启动类加载器不可能“认识”之些代码,该怎么办?

为了解决这个困境,Java设计团队只好引入了一个不太优雅的设计:线程上下文件类加载器(Thread Context ClassLoader)。这个类加载器可以通过java.lang.Thread类的setContextClassLoader()方法进行设置,如果创建线程时还未设置,它将会从父线程中继承一个;如果在应用程序的全局范围内都没有设置过,那么这个类加载器默认就是应用程序类加载器。有了线程上下文类加载器,JNDI服务使用这个线程上下文类加载器去加载所需要的SPI代码,也就是父类加载器请求子类加载器去完成类加载动作,这种行为实际上就是打通了双亲委派模型的层次结构来逆向使用类加载器,已经违背了双亲委派模型,但这也是无可奈何的事情。Java中所有涉及SPI的加载动作基本上都采用这种方式,例如JNDI,JDBC,JCE,JAXB和JBI等。

双亲委派模型的第三次“被破坏” 是由于用户对程序的动态性的追求导致的,例如OSGi的出现。在OSGi环境下,类加载器不再是双亲委派模型中的树状结构,而是进一步发展为网状结构。


Android 中的ClassLoader  

ClassLoader的类型

        Java中的 ClassLoader 可以加载jar文件和Class文件(本质是加载Class文件),这一点在Android中并不适用,因为无论是DVM还是ART,他们加载的不再是Class文件,而是dex文件,这就需要重新设计ClassLoader相关类。

       Android 中的 ClassLoader 类型和 Java 中的 ClassLoader 类型类似,也分为两种类型, 分别是系统类加载器和自定义加载器。其中系统类加载器主要包括3种,分别是 BootClassLoader、PathClassLoader 和 DexClassLoader

  • BootClassLoader
  • 用于加载Android Framework层class文件。
  • PathClassLoader
  • 用于Android应用程序类加载器。可以加载指定的dex,以及jar、zip、apk中的classes.dex
  • DexClassLoader
  • 用于加载指定的dex,以及jar、zip、apk中的classes.dex

很多博客里说PathClassLoader只能加载已安装的apk的dex,其实这说的应该是在dalvik虚拟机上。但现在一般不用关心dalvik了。

1. BootClassLoader

       Android 系统启动时会使用 BootClassLoader 来预加载常用类,与 Java 中的 Bootstrap ClassLoader 不同,它并不是由 C/C++ 码实现的,而是由 Java 实现的, BootClassLoader 的代码如下所示:

libcore/ojluni/src/main/java/java/lang/Classloader.java

class BootClassLoader extends ClassLoader {
 
    private static BootClassLoader instance;
 
    @FindBugsSuppressWarnings("DP_CREATE_CLASSLOADER_INSIDE_DO_PRIVILEGED")
    public static synchronized BootClassLoader getInstance() {
        if (instance == null) {
            instance = new BootClassLoader();
        }
 
        return instance;
    }
    ...
}

BootClassLoader 是 ClassLoader 的内部类,并继承自 ClassLoader。  BootC las sL oader 是一 个单例类,需要注意的是 BootClassLoader 的访问修饰符是 默认 的,只 有在一 个包中才可以访问,因此我们在应用程序中是无法直接调用的。

 
2. DexClassLoader 

DexClassLoader 可以加载 dex 文件以及包含dex的压缩文件(apk 和 jar 文件),不管加载哪种文件, 最终都要加载 dex 文件,为了便于理解和叙述,将 dex 文件以及包  dex 的压缩文件统称为 dex 相关文件。  DexClassLoader 的代码如下所示:
 
libcore/dalvik/src/main/java/dalvik/system/DexClassloader.java

public class DexClassLoader extends BaseDexClassLoader {
    public DexClassLoader(String dexPath, String optimizedDirectory,
            String librarySearchPath, ClassLoader parent) {
        super(dexPath, null, librarySearchPath, parent);
    }
}

DexClassLoader的构造方法有如下4个参数。

       1)dexPath: dex相关文件路径集合,多个路径用文件分隔符分隔,默认文件分隔符为":"。

       2)optimizedDirectory:解压的 dex 文件存储路径,这个路径必须是一个内部存储路径,在一般情况下,使用当前应用程序的私有路径:/data/data/<package name>/... 。

       3)librarySearchPath:包含C/C++库的路径集合,多个路径用文件分隔符分隔,可以为null。

       3)parent:父类加载器。

DexClassLoader 继承自 BaseDexClassLoader,方法都在BaseDexClassLoader 中实现。

3. PathClassLoader

       Android 系统使用 PathClassLoader 来加载系统类和应用程序的类,代码如下所示:

libcore/dalvik/src/main/java/dalvik/system/PathClassloader.java

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);
    }
}

       PathClassLoader 继承自 BaseDexClassLoader ,方法也都在 BaseDexClassLoader 中实现。
 
       PathClassLoader 的构造方法中没有参数 optimizedDirectory, 这是因为 PathClassLoader 已经默认了参数 optimizedDirectory 的值为 /data/dalvik-cache ,很显然 PathClassLoader 无法定义解压的 dex 文件存储路径,因此 PathClassLoader  通常用来加载已经安装的 apk 的 dex 文件(安装的 apk 的 dex 文件会存储在 data/dalvik-cache 中)。

PathClassLoader 与 DexClassLoader 的共同父类是 BaseDexClassLoader 。

public class DexClassLoader extends BaseDexClassLoader {
    public DexClassLoader(String dexPath, String optimizedDirectory, String librarySearchPath, ClassLoader parent) {
        super(dexPath, new File(optimizedDirectory), librarySearchPath, parent);
    }
}

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);
    }
}

可以看到两者唯一的区别在于:创建 DexClassLoader 需要传递一个 optimizedDirectory 参数,并且会将其创建为 File 对象传给 super ,而 PathClassLoader 则直接给到null。因此两者都可以加载指定的dex,以及jar、zip、apk中的classes.dex

PathClassLoader pathClassLoader = new PathClassLoader("/sdcard/xx.dex", getClassLoader());
File dexOutputDir = context.getCodeCacheDir();
DexClassLoader dexClassLoader = new DexClassLoader("/sdcard/xx.dex",dexOutputDir.getAbsolutePath(),null,getClassLoader());

 其实, optimizedDirectory 参数就是dexopt的产出目录(odex)。那 PathClassLoader 创建时,这个目录为null,就意味着不进行dexopt?并不是, optimizedDirectory 为null时的默认路径为:/data/dalvik-cache。

在API 26源码中,将DexClassLoader的optimizedDirectory标记为了 deprecated 弃用,实现也变为了:

public class DexClassLoader extends BaseDexClassLoader {
    /**
     * Creates a {@code DexClassLoader} that finds interpreted and native
     * code.  Interpreted classes are found in a set of DEX files contained
     * in Jar or APK files.
     *
     * <p>The path lists are separated using the character specified by the
     * {@code path.separator} system property, which defaults to {@code :}.
     *
     * @param dexPath the list of jar/apk files containing classes and
     *     resources, delimited by {@code File.pathSeparator}, which
     *     defaults to {@code ":"} on Android
     * @param optimizedDirectory this parameter is deprecated and has no effect since API level 26.
     * @param librarySearchPath the list of directories containing native
     *     libraries, delimited by {@code File.pathSeparator}; may be
     *     {@code null}
     * @param parent the parent class loader
     */
    public DexClassLoader(String dexPath, String optimizedDirectory,
            String librarySearchPath, ClassLoader parent) {
        super(dexPath, null, librarySearchPath, parent);
    }
}
......和PathClassLoader一模一样了!

在API 33中:DexClassLoader和PathClassLoader几乎一模一样了。

public class DexClassLoader extends BaseDexClassLoader {
    public DexClassLoader(String dexPath, String optimizedDirectory, String librarySearchPath, ClassLoader parent) {
        super((String)null, (File)null, (String)null, (ClassLoader)null);
        throw new RuntimeException("Stub!");
    }
}


public class PathClassLoader extends BaseDexClassLoader {
    public PathClassLoader(String dexPath, ClassLoader parent) {
        super((String)null, (File)null, (String)null, (ClassLoader)null);
        throw new RuntimeException("Stub!");
    }

    public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent) {
        super((String)null, (File)null, (String)null, (ClassLoader)null);
        throw new RuntimeException("Stub!");
    }
}

ClassLoader 的继承关系

在Android中运行一个应用程序一般需要用到几种类型的类加载器呢?收下我们来看如下代码:

public class MainActivity extends AppCompatActivity {
    private static final String TAG = MainActivity.class.getSimpleName();
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ClassLoader classLoader = MainActivity.class.getClassLoader();
        while (classLoader != null) {
            Log.d(TAG, classLoader.toString());
            classLoader = classLoader.getParent();
        }
    }
}

首先我们得到 MainActivity 的类加载器,然后通过Log打印出来,接着通过循环打印出当前类的类加载器的父类加载器,直到没有父类加载器实终止循环,打印结果如下所示:

07-27 07:03:57.199 5967-5967/com.lx.class_loader D/MainActivity: dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.lx.class_loader-1/base.apk"],nativeLibraryDirectories=[/data/app/com.lx.class_loader-1/lib/x86, /vendor/lib, /system/lib]]]
07-27 07:03:57.199 5967-5967/com.lx.class_loader D/MainActivity: java.lang.BootClassLoader@88d5d94

可以看到有两种类加载器, 一 种是 PathClassLoader ,另一 种则是 BootClassLoader。  DexPathList 中包 含了很多 apk 的路径,其中 /data/app/com.lx.class_loader-1/base.apk  就是示例应 用安装在手机上的 位置。 DexPathList 是在 BaseDexClassLoader 的构造方法中创建的 ,里面存储了 dex 相关文件的路径,在 ClassLoader 执行双亲委托模式的查找流程时会从 DexPathList 中进行查找。
 
       除了 上面所讲的3 种主要的类加载器外,Android 还提供了其他的类加载器和  ClassLoader 相关类, ClassLoader 的继承关系如图 3 所示。

  可以看到图 3 共有8个  ClassLoader 相关类,其中有一些和 Java 中的 ClassLoader 相关类十分类似,下面简单对它们进行介绍:
       1)ClassLoader 是一个抽象类,其中定义了 ClassLoader 的主要功能。 BootClassLoader 是它的内部类。

       2)SecureClassLoader 类和 JDK8 中的 SecureClassLoader 类的代码是一样的,它继承了抽象类 ClassLoader。SecureClassLoader 并不是 ClassLoader 的实现类,而是拓展了 ClassLoader 类,加入了权限方面的功能,加强了 ClassLoader 的安全性

       3)URLClassLoader 类和 JDK8 中的 URLClassLoader 类的代码是一样的,它继承自 SecureClassLoader ,用来通过 URL 路径从 jar 文件和文件夹中加载类和资掘。

       4)InMemoryDexClassLoader 是 Android 8.0 新增的类加载器,继承自 BaseDexClassLoader ,用于加载内存中的 dex 文件。

       5)BaseDexClassLoader 继承自 ClassLoader ,是抽象类 ClassLoader 的具体实现类, PathClassLoader 、DexClassLoader、 InMemoryDexClassLoader 都继承自它。

ClassLoader 的加载过程

Android中的 ClassLoader 同样遵循了双亲委托模式,ClassLoader 的加载方法为loadClass 方法,这个方法被定义在抽象类ClassLoader 中,代码如下所示:

libcore/ojluni/src/main/java/java/lang/Classloader.java
 

    protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
            // First, check if the class has already been loaded
            Class<?> c = findLoadedClass(name); // ... 1
            if (c == null) {
                try {
                    if (parent != null) {
                        c = parent.loadClass(name, false); // ... 2
                    } else {
                        c = findBootstrapClassOrNull(name); // .... 3
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }
 
                if (c == null) {// ... 4
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    c = findClass(name); // ... 5
                }
            }
            return c;
    }

       ClassLoader 的 loadClass 方法 和前面讲的 loadClass 方法 ( JDK 中  ClassLoader 的  loadClass 方法 )类似。  在注释1处 用来检查 传入的类是否已经加载,如果已经加载就返回该类,如果没有加载就在注释2处判断父加载器是否存在,存在就调 用父加载器的 load Class 方法,如果不存在就调用注释3处的 findBootstrapClassOrNull 方法,这个方法会直接返回 null 。如果注释4 处的代码成 立,说明向上委托流程没有检查出类已经被 加载,就 会执行注释5 处的 find Class 方法 来进行查找流程, find Class 方法 如下所示:
 
libcore/ojluni/src/main/java/java/lang/Classloader.java

    protected Class<?> findClass(String name) throws ClassNotFoundException {
        throw new ClassNotFoundException(name);
    }

在 findClass 方法中直接抛出了异常,这说明 findClass 方法需要子类来实现,BaseDexClassLoader 的代码如下所示:

libcore/dalvik/src/main/java/dalvik/system/BaseDexClassloader.java
 

public class BaseDexClassLoader extends ClassLoader {
 
    private final DexPathList pathList;
 
    public BaseDexClassLoader(String dexPath, File optimizedDirectory,
            String librarySearchPath, ClassLoader parent) {
        super(parent);
        this.pathList = new DexPathList(this, dexPath, librarySearchPath, null); // ... 1
 
        if (reporter != null) {
            reportClassLoaderChain();
        }
    }
    ...
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
        //查找指定的class
        Class c = pathList.findClass(name, suppressedExceptions); // ... 2
        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;
    }
    ...
}

在 BaseDexClassLoader 的构造方法的注释1处创建了 DexPathList。

public DexPathList(ClassLoader definingContext, String dexPath, String librarySearchPath, File optimizedDirectory) {
//.........
// splitDexPath 实现为返回 List<File>.add(dexPath)
// makeDexElements 会去 List<File>.add(dexPath) 中使用DexFile加载dex文件返回 Element数组
this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,
suppressedExceptions, definingContext);
//.........
}

 可以看到DexPathList在创建的时候调用了makeDexElements()方法来创建出了dexElements数组,在makeDexElements之前我们先来看一下splitDexPath()方法,在这个方法中将dexPath目录下的所有程序文件转变成一个File集合,而且dexPath是一个用冒号作为分隔符把多个程序文件目录拼接起来的字符串,如 /data/dexdir1:/data/dexdir2:…。makePathElements方法核心作用就是将指定路径中的所有文件转化成DexFile同时存储到到Element[]这个数组中。

在注释2处调用了 DexPathList 的findClass 方法,代码如下所示:

libcore/dalvik/src/main/java/dalvik/system/DexPathlist.java

    public Class<?> findClass(String name, List<Throwable> suppressed) {
        for (Element element : dexElements) { // ... 1
            Class<?> clazz = element.findClass(name, definingContext, suppressed); // ... 2
            if (clazz != null) {
                return clazz;
            }
        }
 
        if (dexElementsSuppressedExceptions != null) {
            suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
        }
        return null;
    }


       在注释1处遍历 Element 数组 dexElement,在注释2处 调用 Element 的  findClass  方法, Element 是 Dexpa thList 的静态内部类,代码如下所示:
 
libcore/dalvik/src/main/java/dalvik/system/DexPathlist.java

    /*package*/ static class Element {
        private final File path;
        private final DexFile dexFile;
        private ClassPathURLStreamHandler urlHandler;
        private boolean initialized;
 
 
        public Element(DexFile dexFile, File dexZipPath) {
            this.dexFile = dexFile;
            this.path = dexZipPath;
        }
 
        public Element(DexFile dexFile) {
            this.dexFile = dexFile;
            this.path = null;
        }
 
        public Element(File path) {
          this.path = path;
          this.dexFile = null;
        }
 
        ...
 
        public Class<?> findClass(String name, ClassLoader definingContext,
                List<Throwable> suppressed) {
            return dexFile != null ? dexFile.loadClassBinaryName(name, definingContext, suppressed)
                    : null; // ... 1
        }
 
       ...
    }

       从 Element 的构造方法可以看出,其内部封装了 DexFile ,它用于加载dex。在注释1处如果dexFile不为 null 就会调用DexFile 的loadClassBinaryName方法,代码如下所示:

libcore/dalvik/src/main/java/dalvik/system/DexFile.java

    public Class loadClassBinaryName(String name, ClassLoader loader, List<Throwable> suppressed) {
        return defineClass(name, loader, mCookie, this, suppressed);
    }


       在 loadClassBinaryName 方法中调用了defineClass方法,代码如下所示:

libcore/dalvik/src/main/java/dalvik/system/DexFile.java

    private static Class defineClass(String name, ClassLoader loader, Object cookie,
                                     DexFile dexFile, List<Throwable> suppressed) {
        Class result = null;
        try {
            result = defineClassNative(name, loader, cookie, dexFile); // ... 1
        } catch (NoClassDefFoundError e) {
            if (suppressed != null) {
                suppressed.add(e);
            }
        } catch (ClassNotFoundException e) {
            if (suppressed != null) {
                suppressed.add(e);
            }
        }
        return result;
    }

在注释1处调 用了 defineClassN ative 方法来 加载 dex 相关文件,这个方法是 Native 方法 ,这里就不再进行分析, ClassLoader 的加载过程就是遵循着双亲委托模式,  如果委托流程没有检查 到此前加载过传入的类,就调用 ClassLoader 的  findClass 方法 , Java 层最终会调用 DexFile 的  defineClassNative 方法 来执行查找流程。
 

BootClassLoader 的创建

BootClassLoader 是在何时被创建的呢?这得先从 Zygote 进程开始说起, Zygotelnit 的 main 方法如下所示:

frameworks/base/core/java/com/android/internal/os/Zygotelnit.java

 
    public static void main(String argv[]) {
        ...
        try {
            ...
            if (!enableLazyPreload) {
                ...
                preload(bootTimingsTraceLog);
                ...
            } else {
                Zygote.resetNicePriority();
            }
           ...
        } catch (Throwable ex) {
            Log.e(TAG, "System zygote died with exception", ex);
            throw ex;
        } finally {
            zygoteServer.closeServerSocket();
        }
        ...
    }

       在ZygoteInit 的 main 方法中调用了 preload 方法,而preload 方法又调用了ZygoteInit 的 preloadClasses 方法,代码如下所示:

frameworks/base/core/java/com/android/internal/os/Zygotelnit.java

    ...
    private static final String PRELOADED_CLASSES = "/system/etc/preloaded-classes";
    ...
    private static void preloadClasses() {
        final VMRuntime runtime = VMRuntime.getRuntime();
 
        InputStream is;
        try {
            // 获取/system/etc/preloaded-classes文件的FileInputStream
            is = new FileInputStream(PRELOADED_CLASSES); // ... 1
        } catch (FileNotFoundException e) {
            Log.e(TAG, "Couldn't find " + PRELOADED_CLASSES + ".");
            return;
        }
        ...
        try {
            // 将FileInputSteam
            BufferedReader br
                = new BufferedReader(new InputStreamReader(is), 256); // ... 2
 
            int count = 0;
            String line;
            while ((line = br.readLine()) != null) { // ... 3
                // Skip comments and blank lines.
                line = line.trim();
                if (line.startsWith("#") || line.equals("")) {
                    continue;
                }
 
                Trace.traceBegin(Trace.TRACE_TAG_DALVIK, line);
                try {
                    if (false) {
                        Log.v(TAG, "Preloading " + line + "...");
                    }
                    Class.forName(line, true, null); // ... 4
                    count++;
                } catch (ClassNotFoundException e) {
                    Log.w(TAG, "Class not found for preloading: " + line);
                } catch (UnsatisfiedLinkError e) {
                    Log.w(TAG, "Problem preloading " + line + ": " + e);
                } catch (Throwable t) {
                    Log.e(TAG, "Error preloading " + line + ".", t);
                    if (t instanceof Error) {
                        throw (Error) t;
                    }
                    if (t instanceof RuntimeException) {
                        throw (RuntimeException) t;
                    }
                    throw new RuntimeException(t);
                }
                Trace.traceEnd(Trace.TRACE_TAG_DALVIK);
            }
 
            Log.i(TAG, "...preloaded " + count + " classes in "
                    + (SystemClock.uptimeMillis()-startTime) + "ms.");
        } catch (IOException e) {
            Log.e(TAG, "Error reading " + PRELOADED_CLASSES + ".", e);
        } finally {
            ...
        }
    }

preloadClasses 方法用于 Zygote 进程初始化时预加载常用类。在注释1处获取 PRELOADED_CLASSES (值为 system/etc/preloaded-classes )文件的 FilelnputStream, preloaded-classes 文件中存有预加载类的目录,这个文件在系统源码中的路径为 frameworks/base/preloaded-classes,这里列举一些 preloaded-classes 文件中的预载类名称, 如下所示:

android.app.ApplicationLoaders 
android.app.ApplicationPackageManager 
android.app.ApplicationPackageManager$OnPermissionsChangeListenerDelegate 
android.app.ApplicationPackageManager$ResourceName
android.app.ContentProviderHolder 
android.app.ContentProviderHolder$1 
android.app.ContextImpl 
android.app.Contextimpl$ApplicationContentResolver 
android.app.DexLoadReporter 
android.app.Dialog 
android.app.Dialog$ListenersHandler
android.app.DownloadManager 
android.app.Fragment

可以看到  preloaded_classes 文 件中的预加载类 的名称有很多 我们非 常熟 知的。 预加载属于 拿空 间换时间的策 略, Zygote 环境配 置得 越健全越通 用, 应用程序进程需要单 独做的事情也就越 少,预加载除了加 载类 ,还 有预加载资源 和预加 载共享库,因为 不是本节重 点,这里就不再延伸讲下了。回到 preloadClasses 方法 的注释2 处,将 FilelnputStream 封装为 BufferedReader , 并在注释3 处遍历  BufferedReader ,读出所有 预加载类的名 称,每读出一个预加载类 的名称就 调用 注释4 处的代码加载该类, Class 的  forName 方法如下所示:
 
libcore/ojluni/src/main/java/java/lang/Class.java
 

    @CallerSensitive
    public static Class<?> forName(String name, boolean initialize,
                                   ClassLoader loader)
        throws ClassNotFoundException
    {
        if (loader == null) {
            loader = BootClassLoader.getInstance(); // ... 1
        }
        Class<?> result;
        try {
            result = classForName(name, initialize, loader); // ... 2
        } catch (ClassNotFoundException e) {
            Throwable cause = e.getCause();
            if (cause instanceof LinkageError) {
                throw (LinkageError) cause;
            }
            throw e;
        }
        return result;
    }

       在注释1 处创 建了 BootC lassLoader ,并将 BootClassLoader 实 例传入到了注释2 处的 classForName 方法中, classForName 方法 是 Native 方法,它的实现由 C/C++  代码来完成, 如下所示:

    @FastNative
    static native Class<?> classForName(String className, boolean shouldInitialize,
            ClassLoader classLoader) throws ClassNotFoundException;

       Native 方法这里就不再分析了,我们知道了 BootClassLoader 是在 Zygote 进程的 Zygote 入口方法中被创建的,用于加载 preloaded- classes 文件中存有的预加载类。
 

PathClassloader 的创建


       PathClassLoader 的创建也得从 Zygote 进程开始说起, Zygote 进程启动 SystemServer 进程时会调用 Zygotelnit的 handleSystemServerProcess 方法,如下所示:
 
frameworks/base/core/java/com/android/internal/os/Zygotelnit.java
 

    private static Runnable handleSystemServerProcess(ZygoteConnection.Arguments parsedArgs) {
        ...
        if (parsedArgs.invokeWith != null) {
            ...
        } else {
            ClassLoader cl = null;
            if (systemServerClasspath != null) {
                cl = createPathClassLoader(systemServerClasspath, parsedArgs.targetSdkVersion); // ... 1
 
                Thread.currentThread().setContextClassLoader(cl);
            }
 
            /*
             * Pass the remaining arguments to SystemServer.
             */
            return ZygoteInit.zygoteInit(parsedArgs.targetSdkVersion, parsedArgs.remainingArgs, cl);
        }
 
        /* should never reach here */
    }

       在注释1处调用了createPathClassLoader方法,代码如下所示:

frameworks/base/core/java/com/android/internal/os/Zygotelnit.java

    static ClassLoader createPathClassLoader(String classPath, int targetSdkVersion) {
        String libraryPath = System.getProperty("java.library.path");
 
        return ClassLoaderFactory.createClassLoader(classPath, libraryPath, libraryPath,
                ClassLoader.getSystemClassLoader(), targetSdkVersion, true /* isNamespaceShared */,
                null /* classLoaderName */);
    }

       在 createPathClassLoader 方法中有调用了 ClassLoaderFactory 的 createClassLoader 方法,代码如下所示:

frameworks/base/core/java/com/android/internal/os/ClassLoaderFactory.java

    public static ClassLoader createClassLoader(String dexPath,
            String librarySearchPath, ClassLoader parent, String classloaderName) {
        if (isPathClassLoaderName(classloaderName)) {
            return new PathClassLoader(dexPath, librarySearchPath, parent);
        } else if (isDelegateLastClassLoaderName(classloaderName)) {
            return new DelegateLastClassLoader(dexPath, librarySearchPath, parent);
        }
 
        throw new AssertionError("Invalid classLoaderName: " + classloaderName);
    }

在 ClassLoaderFactory 的  createClassLoader 方法 中会创建 PathClassLoader 。讲到这里可以得出结论, PathClassLoader 是在 SystemServer 进程中用工厂模式创建的。
 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值