了解甚至理解ClassLoader
的作用以及工作机制,可以帮助我们更快的上手复杂的框架或者知识。比如热更新,插件化。简单的说,热更新就是从网上下载一个补丁,然后我们的项目通过一个工具把这个补丁拿过来,可以为我所用。这个拿过来为我所用的过程当然很复杂,我们先一点一点来,先来从最简单的知识开始了解。
先说一下类文件,类文件就是我们常常看到的.class
文件,.class
是二进制文件,.class
文件是.java
文件通过编译得来,因为Java
虚拟机(JVM)
并不能直接识别我们写的.java
文件,它能识别.class
文件,所以我们才需要把.java
文件编译成.class
文件。
如果用
C
或者PYTHON
编写的程序正确转换成.class
文件后,java
虚拟机也是可以识别运行的。
ClassLoader
的具体作用就是将class
文件加载到jvm
虚拟机中去,也可以说是将class
的字节码形式转换成内存形式的class
对象,程序就可以正确运行了
Java
类加载
接下来的内容都是参考了一看你就懂,超详细java中的ClassLoader详解
可以直接过去看,我这边拣重要的点说一下。
自带的加载器
-
BootstrapClassLoader
最顶层的加载类,主要加载核心类库,比如说int类等 这个加载器是由C++
编写的 -
ExtentionClassLoader
扩展的类加载器 -
AppclassLoader
也称为SystemAppClass
加载当前应用的classpath
的所有类
每个类加载器都有一个父加载器
先声明一点,父加载器不是父类,这个父加载器跟这个类加载器没有什么直接关系,父加载器是通过构造方法指定的。
上边三个加载器的父子关系 Bootstrap ClassLoader
是Extention ClassLoader
的父加载器,Extention ClassLoader
是Appclass Loader
的父加载器。但是我们在获取Extention ClassLoader
的父加载器的时候会获得一个null
,因为Bootstrap ClassLoader
是由C++
编写,本身就是虚拟机的一部分,不是一个Java
类,也就没办法再Java
代码中获取它的引用了。
classloader
的传递性
程序在运行过程中,遇到了一个未知的类,它会选择哪个 ClassLoader
来加载它呢?虚拟机的策略是使用调用者 Class
对象的 ClassLoader
来加载当前未知的类。何为调用者 Class
对象?就是在遇到这个未知的类时,虚拟机肯定正在运行一个方法调用(静态方法或者实例方法),这个方法挂在哪个类上面,那这个类就是调用者 Class
对象。前面我们提到每个 Class
对象里面都有一个 classLoader
属性记录了当前的类是由谁来加载的。
因为 ClassLoader
的传递性,所有延迟加载的类都会由初始调用 main
方法的这个 ClassLoader
全全负责,它就是 AppClassLoader
。
双亲委托
我自己的理解是当我加载一个class
文件的时候,我先看下我之前是不是加载成功过这个class文件,也就是看下我的class
缓存里边有没有,如果有,就直接使用,如果没有,我就让我的父加载器去加载;父加载器的加载过程和我一样,也是先看自己缓存,如果缓存没有,父加载器再让自己的父加载器去加载,这样递归下去,直到让BootstrapClassLoader
加载,它也是先看缓存,缓存没有就通过方法加载;如果BootstrapClassLoader
也加载不到,那就让子加载器加载,也就是
ExtentionClassLoader
,这个时候就不是找缓存了,而是通过方法加载,找到就返回,找不到就交给子加载器加载。最后如果都没找到,就抛出异常。
image.png
- 一个
AppClassLoader
查找资源时,先看看缓存是否有,缓存有从缓存中获取,否则委托给父加载器。
递归,重复第1部的操作。- 如果
ExtClassLoader
也没有加载过,则由BootstrapClassLoader
出面,它首先查找缓存,如果没有找到的话,就去找自己的规定的路径下,也就是sun.mic.boot.class
下面的路径。找到就返回,没有找到,让子加载器自己去找。BootstrapClassLoader
如果没有查找成功,则ExtClassLoader
自己在java.ext.dirs
路径中去查找,查找成功就返回,查找不成功,再向下让子加载器找。ExtClassLoader
查找不成功,AppClassLoader
就自己查找,在java.class.path
路径下查找。找到就返回。如果没有找到就让子类找,如果没有子类会怎么样?抛出各种异常。
看下代码可能更有助于理解:
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// 首先,检测是否已经加载
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
//父加载器不为空则调用父加载器的loadClass
c = parent.loadClass(name, false);
} else {
//父加载器为空则调用Bootstrap Classloader
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
//父加载器没有找到,则调用findclass
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
//调用resolveClass()
resolveClass(c);
}
return c;
}
}
类加载器是通过loadClass()
方法来加载class
文件的,通过上边的代码,不仅可以理解双亲委托的概念,也可以看出来findClass(name)
这个方法很重要,如果我们自定义类加载器,那肯定就是拿这个方法开刀了。
总结来说,减少开销,如果我缓存有了,或者父亲有了,那就直接用,不要进行加载操作,如果都没有再让父亲加载,加载不了我再加载。
自定义类加载器
步骤:
- 编写一个类继承自
ClassLoader
抽象类。 - 复写它的
findClass()
方法。 - 在
findClass()
方法中调用defineClass()
。
自定义类加载器的时候不益破坏双亲委派规则,不要轻易覆盖loadclass
方法,否则可能会导致自定义加载器无法加载内置的核心类库。
defineClass()
这个方法在编写自定义classloader
的时候非常重要,它能将class
二进制内容转换成Class
对象,如果不符合要求的会抛出各种异常。
一个
ClassLoader
创建时如果没有指定parent
,那么它的parent
默认就是AppClassLoader
。
自定义classLoader
我实践过,是使用Android studio
搞得,发现再Android
环境下根本不能使用java
的这种自定义类加载器方法,原因是Android
屏蔽了defineClass
方法,调用就会报错。
Java
自定义classloader
有想了解的可以跳转一看你就懂,超详细java中的ClassLoader详解-->自定义ClassLoader示例之DiskClassLoader。
总结
Java
的类加载机制是Android
类加载机制的基础,所以我们要进行了解。但是在操作上还是存在区别,所以我也仅作了解。
- 类加载器是用来加载
class
文件的,所以你想自己手动加载自己定义的class
文件,那就自己定义classloader
去加载。 -
classloader
是通过双亲委托的方式加载指定路径下的class
和资源
问题:Class.forName()
加载class
和ClassLoader.loadClass()
加载class
有什么区别?
Class.forName(className)
方法,内部实际调用的方法是 Class.forName(className,true,classloader);
第2个boolean
参数表示类是否需要初始化, Class.forName(className)
默认是需要初始化。
一旦初始化,就会触发目标对象的 static
块代码执行,static
参数也也会被再次初始化。
ClassLoader.loadClass(className)
方法,内部实际调用的方法是 ClassLoader.loadClass(className,false);
第2个 boolean
参数,表示目标对象是否进行链接,false表示不进行链接,由上面介绍可以了解到不进行链接意味着不进行包括初始化等一些列步骤,那么静态块和静态对象就不会得到执行
class.forName()
除了将类的.class
文件加载到jvm
中之外,还会对类进行解释,执行类中的static
块。
而classLoader
只干一件事情,就是将.class
文件加载到jvm
中,不会执行static
中的内容,只有在newInstance
才会去执行static
块。
Class.forName(name, initialize, loader)
带参函数也可控制是否加载static
块。并且只有调用了newInstance()
方法采用调用构造函数,创建类的对象。
网友评论