0
点赞
收藏
分享

微信扫一扫

ClassLoader 学习笔记


文章目录

  • ​​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​​,它有特定的复杂的内部格式。

ClassLoader 学习笔记_加载

每个 ​​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.util.​​、java.io.​​、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​​ 来做,这就是我们常说的「双亲委派」。

ClassLoader 学习笔记_java_02

​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.forName()​​​ 方法可以获取原生类型的 ​​Class​​,而 `ClassLoader.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​​


举报

相关推荐

0 条评论