0%

类加载器与双亲委派模型

前言:总结一下类加载器与双亲委派模型

基础

类的加载过程指通过一个类的全限定名来获取描述此类的二进制字节流,并将其转化为方法区的数据结构,进而生成一个java.lang.Class对象作为方法区这个类各种数据访问的入口。这个过程通过Java中的类加载器(ClassLoader)来完成。

类与类加载器作用

每个类加载器都有一个独立的类名称空间。当要加载两个类时,如果要比较两个类是否相等(包括equals()方法、isAssignableFrom()方法、isInstance()方法),只有在这两个类被同一个类加载器加载的前提下,比较才有意义。否则,即使两个类来自同一个class文件,被同一个JVM加载,但是加载它们的类加载器不同,则这两个类就不相等。这就相当于两个命名空间中的等价类LoaderA::C和LoaderB::C。

类加载器分类

  1. 启动(Bootstrap)类加载器:负责将%JAVA_HOME%/lib目录中或-Xbootclasspath中参数指定的路径中的,并且是虚拟机识别的(按名称)类库加载到JVM中
  2. 扩展(Extension)类加载器:扩展类加载器是由Sun的ExtClassLoader(sun.misc.Launcher$ExtClassLoader)实现的,负责加载%JAVA_HOME%/lib/ext中的所有类库
  3. 系统(System)类加载器:系统类加载器是由Sun的AppClassLoader(sun.misc.Launcher$AppClassLoader)实现的,负责加载%CLASSPATH%路径的类库以及其它自定义的ClassLoader

image-20200102224016056

在虚拟机启动的时候会初始化BootstrapClassLoader,然后在Launcher类中去加载ExtClassLoader、AppClassLoader,并将AppClassLoader的parent设置为ExtClassLoader,并设置线程上下文类加载器。

1
2
3
4
5
6
public static void main(String[] args) throws Exception {
ClassLoader classLoader = XXX.class.getClassLoader();
System.out.println(classLoader);
System.out.println(classLoader.getParent());
System.out.println(classLoader.getParent().getParent());
}

image-20200102224031722

类加载双亲委派机制概念

sun.misc.Launcher

Launcher是JRE中用于启动程序入口main()的类,下面是其构造器方法源码,AppClassLoader、ExtClassLoader是其静态类(默认限定符,包可见)

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
public Launcher() {
Launcher.ExtClassLoader var1;
try {
//加载扩展类类加载器
var1 = Launcher.ExtClassLoader.getExtClassLoader();
} catch (IOException var10) {
throw new InternalError("Could not create extension class loader", var10);
}

try {
//加载应用程序类加载器,并设置parent为extClassLoader
this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
} catch (IOException var9) {
throw new InternalError("Could not create application class loader", var9);
}
//设置默认的线程上下文类加载器为AppClassLoader
Thread.currentThread().setContextClassLoader(this.loader);
String var2 = System.getProperty("java.security.manager");
if (var2 != null) {
SecurityManager var3 = null;
if (!"".equals(var2) && !"default".equals(var2)) {
try {
var3 = (SecurityManager)this.loader.loadClass(var2).newInstance();
} catch (IllegalAccessException var5) {
} catch (InstantiationException var6) {
} catch (ClassNotFoundException var7) {
} catch (ClassCastException var8) {
}
} else {
var3 = new SecurityManager();
}

if (var3 == null) {
throw new InternalError("Could not create SecurityManager: " + var2);
}

System.setSecurityManager(var3);
}

}

VM在加载类时默认采用的是双亲委派机制。即某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次递归(本质上就是loadClass函数的递归调用)。因此,所有的加载请求最终都应该传送到顶层的启动类加载器中。如果父类加载器可以完成这个类加载请求,就成功返回;只有当父类加载器无法完成此加载请求时,子加载器才会尝试自己去加载。事实上,大多数情况下,越基础的类由越上层的加载器进行加载,因为这些基础类之所以称为“基础”,是因为它们总是作为被用户代码调用的API。。

优点

一句话:双亲委派模型能保证基础类仅加载一次,不会让jvm中存在重名的类,保证了java的安全与稳定性

双亲委派模型最大的优点就是让Java类同其类加载器具备带优先级的层次关系。举例。比如我们要加载顶层的Java类—即java.lang.Object类,无论我们用哪个类加载器去加载Object类,这个加载请求最终都会委托给Bootstrap ClassLoader,这样就保证了所有加载器加载的Object类都是同一个类。如果没有双亲委派模型,那就乱了套了,完全可以搞出Root::Object和S1::Object这样两个不同的Object类。

代码实现

java.lang.ClassLoader的loadClass.java

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
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
// 查看是否已经加载过该类,加载过的类会有缓存,是使用native方法实现的
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
// 父类不为空则先让父类加载
if (parent != null) {
c = parent.loadClass(name, false);
} else {
// 父类是null就是BootstrapClassLoader,使用启动类类加载器加载
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();
// 让当前类加载器加载
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(c);
}
return c;
}
}

自定义加载器

继承ClassLorder,实现findClass(String name)方法,即可完成一个带有双亲委派机制的加载器,不能继承AppClassLoader和ExtClassLoader,因为这两个是Launcher的静态内部类,默认访问权限

1、自定义ClassLoader

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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
package com.husky.demo.annotation;

import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;

/**
* @author shency
* @description: 自定义加载器
*/
public class MyClassLoader extends ClassLoader {
//用于读取.Class文件的路径
private String path;
//用于标记这些name的类是先由自身加载的
private Set<String> useMyClassLoaderLoad;

public MyClassLoader(String path, Set<String> useMyClassLoaderLoad) {
this.path = path;
this.useMyClassLoaderLoad = useMyClassLoaderLoad;
}

@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
Class<?> c = findLoadedClass(name);
if (c == null && useMyClassLoaderLoad.contains(name)) {
c = findClass(name);
if (c != null) {
return c;
}
}
return super.loadClass(name);
}

@Override
protected Class<?> findClass(String name) {
//根据文件系统路径加载class文件,并返回byte数组
byte[] classBytes = getClassByte(name);
//调用ClassLoader提供的方法,将二进制数组转换成Class类的实例
return defineClass(name, classBytes, 0, classBytes.length);
}

private byte[] getClassByte(String name) {
String className = name.substring(name.lastIndexOf('.') + 1, name.length()) + ".class";
try {
FileInputStream fileInputStream = new FileInputStream(path + className);
byte[] buffer = new byte[1024];
int length = 0;
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
while ((length = fileInputStream.read(buffer)) > 0) {
byteArrayOutputStream.write(buffer, 0, length);
}
return byteArrayOutputStream.toByteArray();
} catch (IOException e) {
e.printStackTrace();
}
return new byte[]{};
}

public static void main(String[] args) {
//创建一个2s执行一次的定时任务
new Timer().schedule(new TimerTask() {
@Override
public void run() {
// 路径
String path = MyClassLoader.class.getResource("").getPath() + "/";
// 类限定名
String className = "com.husky.demo.annotation.ServiceTest";
Set<String> set = new HashSet<String>();
set.add(className);
//每次都实例化一个ClassLoader,这里传入路径,和需要加载的类名
MyClassLoader myClassLoader = new MyClassLoader(path, set);
try {
//使用自定义的ClassLoader加载类,并调用printVersion方法。
Object o = myClassLoader.loadClass(className).newInstance();
// 使用反射调用方法,如果是强制类型转换,是使用main的AppClassLoader
o.getClass().getMethod("printVersion").invoke(o);
} catch (Exception e) {
e.printStackTrace();
}
}
}, 0, 2000);
}
}

2、ServiceTest

1
2
3
4
5
6
7
8
9
10
/**
* @author shency
* @description: ServiceTest
*/
public class ServiceTest {
public void printVersion(){
System.out.println("当前版本:1.1V");
}

}

3、运行

运行过程中修改ServiceTest的printVersion方法,idea能自动编译,实现了加载并替换

image-20200102224219924

-------------本文结束感谢您的阅读-------------