在 Java 中 .java
源文件会先编译成 .classs
字节码文件,然后借由 JVM 虚拟机将 .class
字节码加载进内存,最终程序得到运行。其中.class
加载过程就是由 类加载器(ClassLoader)
来完成的。
而 Android 与 Java 相似, .java
源文件会先编译成 .dex
文件(.class
的集合),然后 Android 虚拟机( ART虚拟机 和 Dalvik虚拟机)将 .dex
文件加载进内存,最终程序得到运行。dex
加载过程也是由 类加载器(ClassLoader)
完成的。
什么是类加载?
凭借一个类的全限定名得到对应的可以描述该类的二进制字节流,并将这些字节流转化为方法区的某种数据结构,生成一个 java.lang.Class
对象作为方法区这个类各种数据访问入口。这个过程就是类加载。
类加载过程就是由 ClassLoader
来完成的。
类加载器 每个类加载器都有独立的类名称空间。不同类型的类加载器加载同一个 .class
文件得到的类其实是不一样的。只有被同样的加载器加载加载的情况下,对类的比较才有意义(即 Class 对象的 equals
, isAssignableFrom()
, isInstance()
以及 instance
关键字所对应的函数或者表达式的返回结果是否有效)
Java中的类加载器 Java 中的类加载器分为三种:
Bootstrap ClassLoader
(启动类加载器),加载 Java 中的核心类,在 JVM 中由 C++ 实现,其他的类加载器都是在 Java 层的实现。负责将 %JAVA_HOME\lib%
目录下 和 -Xbootclasspath
参数指定目录下的类加载到 JVM 中
Extension ClassLoader
(扩展类加载器),加载 %JAVA_HOME%/lib/ext
和 java.ext.dirs
所指定的路径下的类到 JVM 中
Application ClassLoader
(应用类加载器),加载 %CLASSPATH%
路径下的类。包含了自定义的 ClassLoader
某个具体的类,该由哪个加载器加载呢,加载原则是什么呢?
其加载原则就是 双亲委派
原则。
注:这里的 双亲委派 其实翻译的不够精确,容易让人误解为有两个父类,实际上 Java 是只能单继承的。英文原文为 Parents Delegate
这里应该理解为 父亲委派
原则。
也就是说当某个类加载器要加载某个类时,先委托其父加载器加载,依次会传递至 Bootstrap ClassLoader
。父加载器处理不了的,则由其子加载器来处理。
看一下源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public abstract class ClassLoader { protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { Class<?> c = findLoadedClass(name); if (c == null ) { try { if (parent != null ) { c = parent.loadClass(name, false ); } else { c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { } if (c == null ) { c = findClass(name); } } return c; } }
有了 双亲委派
原则,先会让上层类加载器加载,上层的类加载器优先级高。比如基础的类 java.lang.Object
无论是什么类加载器加载,都会传递给最顶层加载器去加载,最终得到的 Object
类都是同样的 Object
类,保证了一致性。如果没有 双亲委派
原则,那么各个类加载器加载的 Object
都不是同一个类,也就违背了前面提到的类加载器特点。
Android 中的类加载器 Android 虚拟机中加载的是 .dex
文件(多个 .class
合并而来),跟 Java 的类加载机制相似也不同。
Android ClassLoader 源码分析 在这里以 Android 9.0 为例进行类加载机制的源码分析,源码地址:/dalvik/system/
我们来看一下 Android 源码中对 PathClassLoader
和 DexClassLoader
的注释说明:
1 2 3 4 5 6 7 8 9 public class PathClassLoader extends BaseDexClassLoader { }
PathClassLoader
用于加载系统类和应用本身的类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class DexClassLoader extends BaseDexClassLoader { }
DexClassLoader
可以加载包含了 .dex
的 .jar
以及 .apk
中的类。DexClassLoader
可以用于加载 App 未被安装的那一部分的 dex
。需要注意的是,不要把类优化缓存目录放在外部存储空间来避免注入攻击等不安全隐患。
Android 类加载器构造函数 ClassLoader 构造函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 public abstract class ClassLoader { static private class SystemClassLoader { public static ClassLoader loader = ClassLoader.createSystemClassLoader(); } private final ClassLoader parent; private static ClassLoader createSystemClassLoader () { String classPath = System.getProperty("java.class.path" , "." ); String librarySearchPath = System.getProperty("java.library.path" , "" ); return new PathClassLoader(classPath, librarySearchPath, BootClassLoader.getInstance()); } private static Void checkCreateClassLoader () { return null ; } private ClassLoader (Void unused, ClassLoader parent) { this .parent = parent; } protected ClassLoader (ClassLoader parent) { this (checkCreateClassLoader(), parent); } protected ClassLoader () { this (checkCreateClassLoader(), getSystemClassLoader()); } public static ClassLoader getSystemClassLoader () { return SystemClassLoader.loader; } }
getSystemClassLoader()
得到的是一个 PathClassLoader
,它的父类加载器是一个 BootClassLoader
。也就是说,系统默认的类加载器是 PathClassLoader
。
PathClassLoader 构造函数 1 2 3 4 5 6 7 8 9 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
的构造函数。第二个构造参数始终是 null
,表示 optimizedDirectory
始终为 null
。
BaseDexClassLoader 构造函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 public class BaseDexClassLoader extends ClassLoader { private final DexPathList pathList; public BaseDexClassLoader (String dexPath, File optimizedDirectory, String librarySearchPath, ClassLoader parent) { this (dexPath, optimizedDirectory, librarySearchPath, parent, false ); } public BaseDexClassLoader (String dexPath, File optimizedDirectory, String librarySearchPath, ClassLoader parent, boolean isTrusted) { super (parent); this .pathList = new DexPathList(this , dexPath, librarySearchPath, null , isTrusted); if (reporter != null ) { reportClassLoaderChain(); } } }
dexPath
:包含 类或者 资源 的 .jar
/.apk
路径,如果是多个路径,则用 File.pathSeparator
(默认是 :
) 来分隔。当然也可以直接传 dex
的路径。
optimizedDirectory
:在 API 26(Android 8.0)的版本中,它表示 odex
(optimized dex) 读写存放目录,如果传 null
则表示使用系统默认的目录来存储。自 Android 8.0 起,这个参数已经被弃用,不再生效,使用系统默认的目录。
librarySearchPath
:native 库文件存放目录,多个库文件则用 File.pathSeparator
(默认是 :
) 分隔。
parent
: 父类加载器
isTrusted
: 当前加载的 dex
是否受信任,如果受信任则可以访问平台隐藏的API,默认为 false
BaseDexClassLoader
中有一个 DexPathList
类的 pathList
成员变量,它表示 dexPath
下的 .dex
列表。
DexPathList 构造函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 final class DexPathList { private static final String DEX_SUFFIX = ".dex" ; private static final String zipSeparator = "!/" ; private Element[] dexElements; NativeLibraryElement[] nativeLibraryPathElements; public DexPathList (ClassLoader definingContext, String dexPath, String librarySearchPath, File optimizedDirectory) { this (definingContext, dexPath, librarySearchPath, optimizedDirectory, false ); } DexPathList(ClassLoader definingContext, String dexPath, String librarySearchPath, File optimizedDirectory, boolean isTrusted) { this .dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory, suppressedExceptions, definingContext, isTrusted); this .nativeLibraryDirectories = splitPaths(librarySearchPath, false ); this .systemNativeLibraryDirectories = splitPaths(System.getProperty("java.library.path" ), true ); List<File> allNativeLibraryDirectories = new ArrayList<>(nativeLibraryDirectories); allNativeLibraryDirectories.addAll(systemNativeLibraryDirectories); this .nativeLibraryPathElements = makePathElements(allNativeLibraryDirectories); } }
类 Element
用来描述一个 dex
文件所代表的元素。字段 dexElements
则为 dex
文件元素列表,通过 makeDexElements()
方法来初始化。
类 NativeLibraryElement
用来描述一个库文件所代表的元素,字段 nativeLibraryPathElements
则为库文件元素列表,通过 makePathElements
方法来初始化。
makeDexEleemnts()
方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 private static Element[] makeDexElements(List<File> files, File optimizedDirectory, List<IOException> suppressedExceptions, ClassLoader loader, boolean isTrusted) { Element[] elements = new Element[files.size()]; int elementsPos = 0 ; for (File file : files) { if (file.isDirectory()) { elements[elementsPos++] = new Element(file); } else if (file.isFile()) { String name = file.getName(); DexFile dex = null ; if (name.endsWith(DEX_SUFFIX)) { try { dex = loadDexFile(file, optimizedDirectory, loader, elements); if (dex != null ) { elements[elementsPos++] = new Element(dex, null ); } } catch (IOException suppressed) { System.logE("Unable to load dex file: " + file, suppressed); suppressedExceptions.add(suppressed); } } else { try { dex = loadDexFile(file, optimizedDirectory, loader, elements); } catch (IOException suppressed) { suppressedExceptions.add(suppressed); } if (dex == null ) { elements[elementsPos++] = new Element(file); } else { elements[elementsPos++] = new Element(dex, file); } } if (dex != null && isTrusted) { dex.setTrusted(); } } else { System.logW("ClassLoader referenced unknown path: " + file); } } if (elementsPos != elements.length) { elements = Arrays.copyOf(elements, elementsPos); } return elements; } private static DexFile loadDexFile (File file, File optimizedDirectory, ClassLoader loader, Element[] elements) throws IOException { if (optimizedDirectory == null ) { return new DexFile(file, loader, elements); } else { String optimizedPath = optimizedPathFor(file, optimizedDirectory); return DexFile.loadDex(file.getPath(), optimizedPath, 0 , loader, elements); } }
makeDexElements()
就是找到指定文件列表中的所有的 .dex
文件,以数组的形式返回。 loadDexFile
会初始化对应的 DexFile
类,DexFile
代表 .dex
文件。DexFile
在初始化过程中会打开对应的 .dex
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 public final class DexFile { private Object mCookie; private Object mInternalCookie; private final String mFileName; DexFile(String fileName, ClassLoader loader, DexPathList.Element[] elements) throws IOException { mCookie = openDexFile(fileName, null , 0 , loader, elements); mInternalCookie = mCookie; mFileName = fileName; } private static Object openDexFile (String sourceName, String outputName, int flags, ClassLoader loader, DexPathList.Element[] elements) throws IOException { return openDexFileNative(new File(sourceName).getAbsolutePath(), (outputName == null ) ? null : new File(outputName).getAbsolutePath(), flags, loader, elements); } private static native Object openDexFileNative (String sourceName, String outputName, int flags, ClassLoader loader, DexPathList.Element[] elements) ;}
DexClassLoader 构造函数 1 2 3 4 5 6 public class DexClassLoader extends BaseDexClassLoader { public DexClassLoader (String dexPath, String optimizedDirectory, String librarySearchPath, ClassLoader parent) { super (dexPath, null , librarySearchPath, parent); } }
DexClassLoader
跟 PathClassLoader
相似。和前面一样,第二个参数 optimizedDirectory
也从 Android 8.0 开始弃用,不再有效。
类加载器构造函数小结
BaseDexClassLoader
类(PathClassLoader
和 DexClassLoader
)在初始化过程中,会找到其相关的 .dex
列表进行初始化。
系统默认的类加载器就是 PathClassLoader
Android 类加载器加载类过程 类加载器加载类的过程,其实就是 ClassLoader
的方法 loadClass()
ClassLoader#loadClass() 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 public abstract class ClassLoader { public Class<?> loadClass(String name) throws ClassNotFoundException { return loadClass(name, false ); } protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { Class<?> c = findLoadedClass(name); if (c == null ) { try { if (parent != null ) { c = parent.loadClass(name, false ); } else { c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { } if (c == null ) { c = findClass(name); } } return c; } protected Class<?> findClass(String name) throws ClassNotFoundException { throw new ClassNotFoundException(name); } }
我们之前分析过,类加载过程采用的遵循 双亲委派
原则。ClassLoader#findClass()
方法会直接抛出异常,说明 ClassLoader
的子类需要重写该方法才有意义。
BaseDexClassLoader#findClass() 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class BaseDexClassLoader extends ClassLoader { private final DexPathList pathList; @Override protected Class<?> findClass(String name) throws ClassNotFoundException { List<Throwable> suppressedExceptions = new ArrayList<Throwable>(); Class c = pathList.findClass(name, suppressedExceptions); 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
实现了 findClass
方法,实际上是在通过 DexPathList#findClass()
方法在 pathList
中找有没有指定的类。
DexPathList#findClass() 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 final class DexPathList { private Element[] dexElements; public Class<?> findClass(String name, List<Throwable> suppressed) { for (Element element : dexElements) { Class<?> clazz = element.findClass(name, definingContext, suppressed); if (clazz != null ) { return clazz; } } if (dexElementsSuppressedExceptions != null ) { suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions)); } return null ; } static class Element { private final File path; private final DexFile dexFile; public Class<?> findClass(String name, ClassLoader definingContext, List<Throwable> suppressed) { return dexFile != null ? dexFile.loadClassBinaryName(name, definingContext, suppressed) : null ; } } }
从上面源码中,可以发现,类加载是在 dexElements
数组中寻找对应的类,一旦在某一个 .dex
文件中找到指定的类,则不再继续查找,并直接返回。
DexFile#loadClassBinaryName() 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public final class DexFile { public Class loadClassBinaryName (String name, ClassLoader loader, List<Throwable> suppressed) { return defineClass(name, loader, mCookie, this , suppressed); } 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); } catch (NoClassDefFoundError e) { if (suppressed != null ) { suppressed.add(e); } } catch (ClassNotFoundException e) { if (suppressed != null ) { suppressed.add(e); } } return result; } private static native Class defineClassNative (String name, ClassLoader loader, Object cookie, DexFile dexFile) }
最终会调用 native 层的 defineClassNative
方法,来查找 .dex
文件中相应的类。
Android 类加载器加载类过程小结
Android 类加载过程也遵循 双亲委派
原则
BaseDexClassLoader
加载类是顺序的,前面的 .dex
中如果能找到指定的类,则后面的不再查找
总结
Java 和 Android 类加载过程都遵循 双亲委派
原则
PathClassLoader
用于加载系统类和应用本身的类。系统默认的类加载器就是 PathClassLoader
DexClassLoader
可以用于加载 App 未被安装的那一部分的 dex
。
BaseDexClassLoader
加载类是顺序的。