1.Java中的ClassLoader
分为两种类型:系统加载器和自定义类加载器;其中系统加载器包括三种:BootStrap ClassLoader、Extensions ClassLoader和Application ClassLoader。其分别的作用是:
1.1 BootStrap ClassLoader(引导类加载器)
使用C/C++实现的加载器,用于加载指定的JDK核心类库,像java.lang;java.reflect;java.utils这一类系统类。它用它来加载一下路径的类库:
$JAVA_HOME/jre/lib目录下
-Xbootclasspath参数指定下的目录
java虚拟机的启动时就是通过BootStrap ClassLoader来创建一个初始类来完成的(类似main函数)。由于BootStrap ClassLoader是使用C/C++实现的,所以不能为Java代码访问到。
BootStrap ClassLoader并不没有继承java.lang.ClassLoader。
1.2 Extensions ClassLoader(扩展类加载器)
java语言中表现为ExtClassLoader
,用于加载Java的扩展类。在Java9之后,更名为PlatformClassLoader
。它提供了除了系统类之外的额外功能,它主要用来加载一下目录下的类库:
$JAVA_HOME/jre/lib/ext目录下
系统属性java.ext.dir所指定的目录
1.3 Application ClassLoader(应用程序加载器)
java语言中实现为AppClassLoader
,用于加载一下路径的类库
当前程序的Classpath目录
系统属性java.class.path所指定的目录
2. 双亲委派模式
java类加载器查找Class文件时,就是采用双亲委派模式。基本套路为:
1.先查找该class文件时是否加载过,如果加载过,直接返回
2.没有加载过,先查找class的任务委派给自己的父加载器
3.如果还没有找到,就再委派给自己的父类加载器
…
4. 最后将抛给顶层的BootStrap ClassLoader,如果BC找到了就返回;否则就按原路返回,叫子类加载器去加载目标class,如果没有找到,在叫子子类加载器去加载
5.最终找到就返回,否则抛出异常
画图显示大致过程:
对于我们需要加载的路劲下的class文件,此时该class文件不在当前程序目录中,此时我们就需要用到了自定义classLoader了。自定义的classLoader需要继承java.lang.ClassLoader,并复写loadClass方法。一般寻找class文件的套路如下:
- 自定义classLoader会在自己的缓存中寻找该class文件是否被加载过,如果有直接返回;如果没有,则将任务委托给我们的AppClassLoader;
- 按照上图的方式进行递归查找;
- 一直委托到BootStrap ClassLoader,如果BS的缓存中不存在该class文件,则在$JAVA_HOME/jre/lib或者 -Xbootclasspath参数指定下的目录 中查找,如果找到就直接返回Class,没有则交给子加载器ExtClassLoader查找;
- 同理,ExtClassLoader加载机制到AppClassLoader最后到自定义的CustomClassLoader,最终没有找到就跑出异常。
2.1 双亲委派的缘由
java的创建者们为什么会花这么大力气来实现这个类的加载机制呢?
主要是为了安全起见!!!如果不使用这种双亲委派模式,我们也可以自定义一个java.lang.String类来代替系统的java.lang.String类,这样很显然会造成不可预测的安全隐患,采用双亲委派模式可以使得系统的String类在VM启动被优先加载,这样我们自定义的String类将不能代替系统的String类。
ps: java虚拟机认为两个类是同一个类需要满足两个条件:
- 两个类名一致[packagename + classname]
- 被同一个类加载器加载
另外双亲委派还有一个好处就是可以避免类被重复加载,如果该类已经加载过,就没有必要重复加载一次,直接读取缓存即可。
3. 自定义classLoader
只需要继承ClassLoader,并重写findClass即可。自定义的ClassLoader只能过加载指定目录下的jar包或者Class文件,所以我们需要指定所在的目录即可,下面就是一个例子:
import java.io.*;
public class MyClassLoader extends ClassLoader{
//定义指定的目录
private String path;
public MyClassLoader(String path) {
this.path = path;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
System.out.println("findClass,name:" + name);
byte[] bytes = loadClassByte(name);
if(null == bytes) {
throw new ClassNotFoundException(name + " class not found");
}
//直接调用父类定义的natvive方法加载指定的Class类
return defineClass(name,bytes,0,bytes.length);
}
//将目下下的class文件转换为二进制文件
private byte[] loadClassByte(String name){
String fileName = getFileName(name);
File file = new File(path, fileName);
InputStream inputStream = null ;
ByteArrayOutputStream outputStream = null ;
byte[] bytes = new byte[1024 * 4];
int length = -1;
try {
inputStream = new FileInputStream(file);
outputStream = new ByteArrayOutputStream();
while ((length = inputStream.read(bytes)) != -1) {
outputStream.write(bytes,0,length);
}
return outputStream.toByteArray();
}catch (IOException e){
e.printStackTrace();
}finally {
closeQuite(inputStream);
closeQuite(outputStream);
}
return null ;
}
private String getFileName(String name) {
int index = name.lastIndexOf(".");
if(index == -1) {
return name.concat(".class");
}
return name.substring(index + 1) .concat(".class");
}
private void closeQuite(Closeable closeable){
if(null != closeable ) {
try {
closeable.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
然后我写个测试Entity:
package com.xing;
public class Person {
private String name ;
public int age ;
public Person(String name , int age) {
this.name = name;
this.age = age ;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
我们再来写个测试:
public static void main(String[] args) throws Exception {
//目录自己定义
MyClassLoader classLoader = new MyClassLoader("D:\\java\\idea\\work\\out\\production\\work\\com\\xing");
Class clazz = classLoader.findClass("com.xing.Person");
Object object = clazz.getDeclaredConstructor(String.class, int.class).newInstance("zhangsan", 18);
System.out.println(object);
System.out.println(Person.class.getClassLoader());
System.out.println(object.getClass().getClassLoader());
System.out.println(object instanceof Person);
}
我们看一下输出:
findClass,name:com.xing.Person
Person{name='zhangsan', age=18}
jdk.internal.loader.ClassLoaders$AppClassLoader@726f3b58
com.xing.MyClassLoader@58ceff1
false
看最后两行,我们可以看出使用自定义的ClassLoader可以加载指定的Class文件,最后一行可以看出object instanceOf Person显示结果是false,即二者不是同一个类。因为两个类的classloader是不一致的,所以说明判断两个类是否一致不仅需要看类名,还需要看两者的类加载器是否一致。
4. Andorid中的ClassLoader
4.1 在Android中ClassLoader的类型
和Java中不一致,Java虚拟机中加载的是class文件,而Android中加载的是dex文件。二者是有区别的。Android中classLoader也分为两种类型:系统加载器和自定义加载器。其中系统加载器分为BootClassLoader、PathClassLoader和DexClassLoader。
4.2 BootClassLoader
Android系统启动时会使用BootClassLoader来加载常用类,与Java的BootStrap ClassLoader不同,BootClassLoader使用的是java编写的:
### /libcore/ojluni/src/main/java/java/lang/ClassLoader.java
1336 class BootClassLoader extends ClassLoader {
1337
1338 private static BootClassLoader instance;
1339
1340 @FindBugsSuppressWarnings("DP_CREATE_CLASSLOADER_INSIDE_DO_PRIVILEGED")
1341 public static synchronized BootClassLoader getInstance() {
1342 if (instance == null) {
1343 instance = new BootClassLoader();
1344 }
1345
1346 return instance;
1347 }
BootClassLoader是ClassLoader的内部类,并继承了ClassLoader.BootClassLoader是一个单例类。而且BootClassLoader的修饰符是默认的,只能在同一个包可以访问,因此在外部是不能调用的。
4.3 DexClassLoader
DexClassLoader可以加载dex文件以及包含dex的压缩文件(apk和jar文件),DexClassLoader的代码如下:
#### /libcore/dalvik/src/main/java/dalvik/system/DexClassLoader.java
36public class DexClassLoader extends BaseDexClassLoader {
37 /**
38 * Creates a {@code DexClassLoader} that finds interpreted and native
39 * code. Interpreted classes are found in a set of DEX files contained
40 * in Jar or APK files.
41 *
42 * <p>The path lists are separated using the character specified by the
43 * {@code path.separator} system property, which defaults to {@code :}.
44 *
45 * @param dexPath the list of jar/apk files containing classes and
46 * resources, delimited by {@code File.pathSeparator}, which
47 * defaults to {@code ":"} on Android
48 * @param optimizedDirectory directory where optimized dex files
49 * should be written; must not be {@code null}
50 * @param librarySearchPath the list of directories containing native
51 * libraries, delimited by {@code File.pathSeparator}; may be
52 * {@code null}
53 * @param parent the parent class loader
54 */
55 public DexClassLoader(String dexPath, String optimizedDirectory,
56 String librarySearchPath, ClassLoader parent) {
57 super(dexPath, new File(optimizedDirectory), librarySearchPath, parent);
58 }
59}
参数为:
- dexPath jar/apk文件路径的集合,默认分割符为:
2.optimizedDirectory 解压dex文件的路径
3.librarySearchPath 本地文件库(c/c++文件)
parent 父classLoader
4.4 PathClassLoader
加载系统类和应用程序类
#### /libcore/dalvik/src/main/java/dalvik/system/PathClassLoader.java
25 public class PathClassLoader extends BaseDexClassLoader {
26 /**
27 * Creates a {@code PathClassLoader} that operates on a given list of files
28 * and directories. This method is equivalent to calling
29 * {@link #PathClassLoader(String, String, ClassLoader)} with a
30 * {@code null} value for the second argument (see description there).
31 *
32 * @param dexPath the list of jar/apk files containing classes and
33 * resources, delimited by {@code File.pathSeparator}, which
34 * defaults to {@code ":"} on Android
35 * @param parent the parent class loader
36 */
37 public PathClassLoader(String dexPath, ClassLoader parent) {
38 super(dexPath, null, null, parent);
39 }
40
41 /**
42 * Creates a {@code PathClassLoader} that operates on two given
43 * lists of files and directories. The entries of the first list
44 * should be one of the following:
45 *
46 * <ul>
47 * <li>JAR/ZIP/APK files, possibly containing a "classes.dex" file as
48 * well as arbitrary resources.
49 * <li>Raw ".dex" files (not inside a zip file).
50 * </ul>
51 *
52 * The entries of the second list should be directories containing
53 * native library files.
54 *
55 * @param dexPath the list of jar/apk files containing classes and
56 * resources, delimited by {@code File.pathSeparator}, which
57 * defaults to {@code ":"} on Android
58 * @param librarySearchPath the list of directories containing native
59 * libraries, delimited by {@code File.pathSeparator}; may be
60 * {@code null}
61 * @param parent the parent class loader
62 */
63 public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent) {
64 super(dexPath, null, librarySearchPath, parent);
65 }
66}
5. ClassLoader的继承关系
在Andorid代码中,打印类加载器:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ClassLoader loader = MainActivity.this.getClassLoader();
while (null != loader) {
Log.d("TAG" , "--->>" + loader);
loader = loader.getParent();
}
}
打印的结果为:
dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.yincheng.androidtest-12TPNGHK_JPvV-NhgR5x6g==/base.apk",
zip file "/data/app/com.yincheng.androidtest-12TPNGHK_JPvV-NhgR5x6g==/split_lib_dependencies_apk.apk",
zip file "/data/app/com.yincheng.androidtest-12TPNGHK_JPvV-NhgR5x6g==/split_lib_resources_apk.apk",
zip file "/data/app/com.yincheng.androidtest-12TPNGHK_JPvV-NhgR5x6g==/split_lib_slice_0_apk.apk",
zip file "/data/app/com.yincheng.androidtest-12TPNGHK_JPvV-NhgR5x6g==/split_lib_slice_1_apk.apk",
zip file "/data/app/com.yincheng.androidtest-12TPNGHK_JPvV-NhgR5x6g==/split_lib_slice_2_apk.apk",
zip file "/data/app/com.yincheng.androidtest-12TPNGHK_JPvV-NhgR5x6g==/split_lib_slice_3_apk.apk",
zip file "/data/app/com.yincheng.androidtest-12TPNGHK_JPvV-NhgR5x6g==/split_lib_slice_4_apk.apk",
zip file "/data/app/com.yincheng.androidtest-12TPNGHK_JPvV-NhgR5x6g==/split_lib_slice_5_apk.apk",
zip file "/data/app/com.yincheng.androidtest-12TPNGHK_JPvV-NhgR5x6g==/split_lib_slice_6_apk.apk",
zip file "/data/app/com.yincheng.androidtest-12TPNGHK_JPvV-NhgR5x6g==/split_lib_slice_7_apk.apk",
zip file "/data/app/com.yincheng.androidtest-12TPNGHK_JPvV-NhgR5x6g==/split_lib_slice_8_apk.apk",
zip file "/data/app/com.yincheng.androidtest-12TPNGHK_JPvV-NhgR5x6g==/split_lib_slice_9_apk.apk"],
nativeLibraryDirectories=[/data/app/com.yincheng.androidtest-12TPNGHK_JPvV-NhgR5x6g==/lib/x86, /system/lib, /vendor/lib]]]
D/TAG: --->>java.lang.BootClassLoader@cc90c73
可以看出存在两种类加载器,一种是PathClassLoader,另外一种是BootClassLoader. dexPathList中包含了很多apk的路径,其中/data/app/com.yincheng.androidtest-12TPNGHK_JPvV-NhgR5x6g==/base.apk
。
6. Android中双亲委派过程
同样需要查看ClassLoader的findClass方法:
/** <@link>
http://androidxref.com/8.0.0_r4/xref/libcore/ojluni/src/main/java/java/lang/ClassLoader.java#180
*/
359 protected Class<?> loadClass(String name, boolean resolve)
360 throws ClassNotFoundException
361 {
362 // First, check if the class has already been loaded
363 Class<?> c = findLoadedClass(name);
364 if (c == null) {
365 try {
366 if (parent != null) {
367 c = parent.loadClass(name, false);
368 } else {
369 c = findBootstrapClassOrNull(name); //return null at the time
370 }
371 } catch (ClassNotFoundException e) {
372 // ClassNotFoundException thrown if class not found
373 // from the non-null parent class loader
374 }
375
376 if (c == null) {
377 // If still not found, then invoke findClass in order
378 // to find the class.
379 c = findClass(name);
380 }
381 }
382 return c;
383 }
大致过程上面注释已经很清楚了,如果没有找到,就委托给父类加载器;如果都没有,则执行findClass,其中findClass中:
404 protected Class<?> findClass(String name) throws ClassNotFoundException {
405 throw new ClassNotFoundException(name);
406 }
看来需要子类来实现了,我们看一下它的子类BaseDexClassLoader的findClass方法:
### http://androidxref.com/8.0.0_r4/xref/libcore/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java#30
88 @Override
89 protected Class<?> findClass(String name) throws ClassNotFoundException {
90 List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
91 Class c = pathList.findClass(name, suppressedExceptions);
92 if (c == null) {
93 ClassNotFoundException cnfe = new ClassNotFoundException(
94 "Didn't find class \"" + name + "\" on path: " + pathList);
95 for (Throwable t : suppressedExceptions) {
96 cnfe.addSuppressed(t);
97 }
98 throw cnfe;
99 }
100 return c;
101 }
可以看出是由pathList中获取的,那么这个pathList是从哪里来的呢?是从构造方法里面传入的:
### http://androidxref.com/8.0.0_r4/xref/libcore/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java#30
82 public BaseDexClassLoader(ByteBuffer[] dexFiles, ClassLoader parent) {
83 // TODO We should support giving this a library search path maybe.
84 super(parent);
85 this.pathList = new DexPathList(this, dexFiles);
86 }
然后我们看一下DexPathList中findClass方法:
### http://androidxref.com/8.0.0_r4/xref/libcore/dalvik/src/main/java/dalvik/system/DexPathList.java#86
464 public Class<?> findClass(String name, List<Throwable> suppressed) {
465 for (Element element : dexElements) {
466 Class<?> clazz = element.findClass(name, definingContext, suppressed);
467 if (clazz != null) {
468 return clazz;
469 }
470 }
471
472 if (dexElementsSuppressedExceptions != null) {
473 suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
474 }
475 return null;
476 }
477
然后再看Element中findClass的方法:
/*package*/ static class Element {
675 public Class<?> findClass(String name, ClassLoader definingContext,
676 List<Throwable> suppressed) {
677 return dexFile != null ? dexFile.loadClassBinaryName(name, definingContext, suppressed)
678 : null;
679 }
这次是dexFile来loadClassBinaryName了,我们查询一下这个DexFile:
### http://androidxref.com/8.0.0_r4/xref/libcore/dalvik/src/main/java/dalvik/system/DexFile.java#42
274 public Class loadClassBinaryName(String name, ClassLoader loader, List<Throwable> suppressed) {
275 return defineClass(name, loader, mCookie, this, suppressed);
276 }
277
278 private static Class defineClass(String name, ClassLoader loader, Object cookie,
279 DexFile dexFile, List<Throwable> suppressed) {
280 Class result = null;
281 try {
282 result = defineClassNative(name, loader, cookie, dexFile);
283 } catch (NoClassDefFoundError e) {
284 if (suppressed != null) {
285 suppressed.add(e);
286 }
287 } catch (ClassNotFoundException e) {
288 if (suppressed != null) {
289 suppressed.add(e);
290 }
291 }
292 return result;
293 }
到了最后,需要调用defineClassNative本地方法查找相关的Class文件。
ClassLoader的加载过程就是遵循了双亲委派模式,如果委托流程没有检查到此前传入的类,就调用ClassLoader的findClass方法,Java层最终会调用DexFile的defineClassNative来执行查找流程。
7.总结
基本如下:
1.Java的顶级类加载器BootStrap ClassLoader是有C/C++编写,Java层不能访问到;
2.Android的顶级类加载器BootClassLoader是由Java语言编写,Java层可以访问;Android类的加载器继承关系比java中复杂一些,提供的功能比较多。
3. java和Android的类加载器加载的文件不一致,前者是class文件,或者是dex文件;正因为如此,Android中没有了ExtClassLoader和AppClassLoader,取代他们的是PathClassLoader和DexClassLoader。
学习之【刘望舒—>[Andorid进阶解密] https://item.jd.com/28229567338.html】