理解Java ClassLoader & Android ClassLoader
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