ClassLoader

类从编译到执行的过程

  • 编译器将源文件转化为字节码文件
  • ClassLoader将字节码转化为JVM中的Class<T>对象
  • JVM利用Class<T>对象实例化为T对象

ClassLoader

ClassLoader在java中有着非常重要的作用,它主要工作在class装载的加载阶段,其主要作用是从外部系统获得class二进制数据流,它是java的核心组件,所有的class都是由ClassLoader进行加载的,ClassLoader负责通过将class文件里的二进制数据流装进系统,然后交给java虚拟机进行连接、初始化等操作。

ClassLoader的种类

  1. BootStrapClassLoader:C++ 编写,加载核心库java.*(例如java.lang)
    1. 通常这些核心类被签名,不能被替换掉
  2. ExtClassLoader:Java编写,加载扩展库javax.*
    1. 是用户可见的ClassLoader
  3. AppClassLoader:Java编写,加载程序所在目录(class.path)
  4. 自定义ClassLoader:Java编写,定制化加载

自定义ClassLoader的实现

ClassLoader的关键函数:

1
2
3
4
// 用来寻找class文件,包括怎么读进二进制流,怎么对其进行处理等。
protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
1
2
3
4
5
// 定义一个类,参数byte[] b就是读进来的字节流,返回整合完成的Class
@Deprecated
protected final Class<?> defineClass(byte[] b, int off, int len) throws ClassFormatError {
return defineClass(null, b, off, len, null);
}

自定义的实现,目标把桌面的Wali.class加载进来:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
public class MyClassLoader extends ClassLoader{

/** 路径 */
private String path;

/** 名称 */
private String classLoaderName;

/** 全参构造函数 */
public MyClassLoader(String path, String classLoaderName) {
this.path = path;
this.classLoaderName = classLoaderName;
}

//用户寻找类文件
@Override
public Class findClass(String name){
byte[] b = loadClassData(name);
return defineClass(name,b,0,b.length);
}

/** 用于加载类文件 */
private byte[] loadClassData(String name) {
/** 全路径1 */
name = path + name + ".class";
/** jdk1.8新特性,不用手动关闭 */
try(InputStream in =
new FileInputStream(new File(name));
ByteArrayOutputStream out = new ByteArrayOutputStream();
) {
/** 进行循环 */
int i = 0;
while ((i = in.read()) != -1){
/** 写文件 */
out.write(i);
}
/** 进行返回 */
return out.toByteArray();
} catch (IOException e) {
e.printStackTrace();
return null;
}

}
}

使用:

1
2
3
4
5
6
7
8
public class ClassLoaderChecker {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
MyClassLoader m = new MyClassLoader("~/Desktop/", "myClassLoader");
Class c = m.loadClass("Wali");
System.out.println(c.getClassLoader());
c.newInstance();
}
}

ClassLoader的双亲委派机制

截屏2021-01-13 下午11.11.43

首先自底向上从自定义类加载器开始查看类是否曾经被此加载器加载过,如果加载过直接返回,如果没加载过则委派给patrnt查找。

如果直到Bootstrap都没加载过,就会自顶向下在各自对应的目录下寻找此类,如果找到了就加载进来,没找到就委派给儿子去寻找。

为什么使用双亲委派

  • 避免多份同样字节码的加载

类的加载方式

  1. 隐式加载:new
  2. 显式加载:loadClass,forName等

loadClass和forName的区别

类的装载过程

截屏2021-01-14 上午12.04.21

Class.forName得到的class是已经初始化完成的(第三步完成)

ClassLoader.loadClass得到的class是还没有链接的(第一步完成)

使用示例:

SQL的Driver要forName来引入,因为其内有static代码段,只在初始化时才运行。

Spring 的IOC(控制反转)就要用loadClass来加载,因为Spring IOC的加载策略是Lazy Loading,延迟加载,先载入进来,要用到的时候再进行初始化,从而加快加载速度,所以要用loadClass