文章目录
- 1. ClassLoader 是做什么的?
- 2. JVM 类加载流程
- 3. 延迟加载
- 4. ClassLoader 传递性
- 5. 双亲委派
- 6. Class.forName
- 7. 自定义加载器
- Class.forName` vs `ClassLoader.loadClass
- 参考链接
1. ClassLoader 是做什么的?
用来加载 Class 的。负责将 Class 的字节码形式转换成 内存中的 Class 对象,字节码可以来自于磁盘文件 *.class,也可以是 jar 包里的 *.class,也可以是
.dex
中的 class,字节码的本质就是一个字节数组
[]byte
,它有特定的复杂的内部格式。
每个
Class
对象的内部都有一个
classLoader
字段来标识自己是由哪个
ClassLoader
加载的。
ClassLoader
就像一个容器,里面装了很多已经加载的
Class
对象。
class Class<T> {
...
private final ClassLoader classLoader;
...
}
2. JVM 类加载流程
Java语言系统自带有三个类加载器:
-
负责加载 Bootstrap ClassLoader
运行时核心类,这些类位于 JVM
文件中,我们常用内置库 $JAVA_HOME/lib/rt.jar
都在里面,比如 java.xxx.*
、java.io.java.util.
、、java.nio.
等等。这个 java.lang.
比较特殊,它是由 ClassLoader
代码实现的,我们将它称之为「根加载器」。C
-
负责加载 Extention ClassLoader
扩展类,比如 JVM
系列、内置的 swing
引擎、js
解析器 等等,这些库名通常以 xml
开头,它们的 javax
包位于 jar
中,有很多 $JAVA_HOME/lib/ext/*.jar
包。jar
-
才是直接面向我们用户的加载器,它会加载 Appclass Loader
环境变量里定义的路径中的 Classpath
包和目录。我们自己编写的代码以及使用的第三方 jar 包通常都是由它来加载的。jar
AppClassLoader
可以由
ClassLoader
类提供的静态方法
getSystemClassLoader()
得到,它就是我们所说的「系统类加载器」,我们用户平时编写的类代码通常都是由它加载的。当我们的
main
方法执行的时候,这第一个用户类的加载器就是
AppClassLoader
。
3. 延迟加载
JVM
运行并不是一次性加载所需要的全部类的,它是按需加载,也就是延迟加载。程序在运行的过程中会逐渐遇到很多不认识的新类,这时候就会调用
ClassLoader
来加载这些类。加载完成后就会将
Class
对象存在
ClassLoader
里面,下次就不需要重新加载了。
比如你在调用某个类的静态方法时,首先这个类肯定是需要被加载的,但是并不会触及这个类的实例字段,那么实例字段的类别
Class
就可以暂时不必去加载,但是它可能会加载静态字段相关的类别,因为静态方法会访问静态字段。而实例字段的类别需要等到你实例化对象的时候才可能会加载。
4. ClassLoader 传递性
程序在运行过程中,遇到了一个未知的类,它会选择哪个
ClassLoader
来加载它呢?虚拟机的策略是使用调用者
Class
对象的
ClassLoader
来加载当前未知的类。何为调用者
Class
对象?就是在遇到这个未知的类时,虚拟机肯定正在运行一个方法调用(静态方法或者实例方法),这个方法挂在哪个类上面,那这个类就是调用者
Class
对象。前面我们提到每个
Class
对象里面都有一个 classLoader 属性记录了当前的类是由谁来加载的。
因为
ClassLoader
的传递性,所有延迟加载的类都会由初始调用
main
方法的这个
ClassLoader
全全负责,它就是
AppClassLoader
。
5. 双亲委派
前面我们提到
AppClassLoader
只负责加载
Classpath
下面的类库,如果遇到没有加载的系统类库怎么办,
AppClassLoader
必须将系统类库的加载工作交给
BootstrapClassLoader
和
ExtensionClassLoader
来做,这就是我们常说的「双亲委派」。
AppClassLoader
在加载一个未知的类名时,它并不是立即去搜寻
Classpath
,它会首先将这个类名称交给
ExtensionClassLoader
来加载,如果
ExtensionClassLoader
可以加载,那么
AppClassLoader
就不用麻烦了。否则它就会搜索
Classpath
。
而
ExtensionClassLoader
在加载一个未知的类名时,它也并不是立即搜寻
ext
路径,它会首先将类名称交给
BootstrapClassLoader
来加载,如果
BootstrapClassLoader
可以加载,那么
ExtensionClassLoader
也就不用麻烦了。否则它就会搜索
ext
路径下的
jar
包。
这三个
ClassLoader
之间形成了级联的父子关系,每个
ClassLoader
都很懒,尽量把工作交给父亲做,父亲干不了了自己才会干。每个
ClassLoader
对象内部都会有一个 parent 属性指向它的父加载器。
class ClassLoader {
...
private final ClassLoader parent;
...
}
值得注意的是图中的
ExtensionClassLoader
的
parent
指针画了虚线,这是因为它的
parent
的值是
null
,当
parent
字段是
null
时就表示它的父加载器是「根加载器」。如果某个
Class
对象的
classLoader
属性值是
null
,那么就表示这个类也是「根加载器」加载的。注意这里的
parent
不是
super
不是父类,只是
ClassLoader
内部的字段。
6. Class.forName
forName
方法使用调用者
Class
对象的
ClassLoader
来加载目标类。不过
forName
还提供了多参数版本,可以指定使用哪个
ClassLoader
来加载。
Class<?> forName(String name, boolean initialize, ClassLoader cl)
通过这种形式的
forName
方法可以突破内置加载器的限制,通过使用自定类加载器允许我们自由加载其它任意来源的类库。根据
ClassLoader
的传递性,目标类库传递引用到的其它类库也将会使用自定义加载器加载。
Android hotfix 中的应用;
/**
* 通过反射获取BaseDexClassLoader对象中的PathList对象
*
* @param baseDexClassLoader BaseDexClassLoader对象
* @return PathList对象
*/
public static Object getPathList(Object baseDexClassLoader)
throws NoSuchFieldException, IllegalAccessException, IllegalArgumentException, ClassNotFoundException {
return getField(baseDexClassLoader, Class.forName("dalvik.system.BaseDexClassLoader"), "pathList");
}
7. 自定义加载器
ClassLoader
里面有三个重要的方法
loadClass()
、
findClass()
和
defineClass()
。
-
方法是加载目标类的入口,它首先会查找当前loadClass()
以及它的双亲里面是否已经加载了目标类,如果没有找到就会让双亲尝试加载,如果双亲都加载不了,就会调用ClassLoader
让自定义加载器自己来加载目标类。findClass()
-
方法是需要子类来覆盖的,不同的加载器将使用不同的逻辑来获取目标类的字节码。findClass()
- 拿到这个字节码之后再调用
方法将字节码转换成defineClass()
对象。Class
class ClassLoader {
// 加载入口,定义了双亲委派规则
Class loadClass(String name) {
// 是否已经加载了
Class t = this.findFromLoaded(name);
if(t == null) {
// 交给双亲
t = this.parent.loadClass(name)
}
if(t == null) {
// 双亲都不行,只能靠自己了
t = this.findClass(name);
}
return t;
}
// 交给子类自己去实现
Class findClass(String name) {
throw ClassNotFoundException();
}
// 组装Class对象
Class defineClass(byte[] code, String name) {
return buildClassFromCode(code, name);
}
}
class CustomClassLoader extends ClassLoader {
Class findClass(String name) {
// 寻找字节码
byte[] code = findCodeFromSomewhere(name);
// 组装Class对象
return this.defineClass(code, name);
}
}
Class.forNamevsClassLoader.loadClass
Class<?> x = Class.forName("[I");
System.out.println(x);
x = ClassLoader.getSystemClassLoader().loadClass("[I");
System.out.println(x);
---------------------
class [I
Exception in thread "main" java.lang.ClassNotFoundException: [I
...
参考链接
- 老大难的 Java ClassLoader 再不理解就老了
- 一看你就懂,超详细java中的ClassLoader详解
- 深入理解JVM之ClassLoader