java双亲委派模式

为什么需要双亲委派机制

双亲委派机制是Java虚拟机(JVM)中的一种类加载机制。

在Java中,类加载器(ClassLoader)负责将类的字节码加载到内存中,并创建对应的Class对象。当一个类被加载时,JVM会按照一定的顺序去搜索类的定义。双亲委派机制就是基于这种搜索顺序来工作的。

具体来说,当一个类加载器收到加载类的请求时,它首先会将这个请求委派给它的父类加载器去处理。如果父类加载器能够找到并加载这个类,那么加载过程就结束了。如果父类加载器无法找到这个类,那么子类加载器才会尝试加载这个类。

这种双亲委派机制的好处在于可以避免类的重复加载和冲突。当一个类被加载后,它会被缓存起来,下次再次加载时可以直接使用缓存的结果,提高了加载的效率。另外,由于父类加载器优先加载类,所以可以保证类的一致性,避免了不同类加载器加载同一个类的问题。

双亲委派机制还有助于保护核心类库的安全性。JVM中有一个根类加载器(Bootstrap ClassLoader),它负责加载核心类库,如java.lang包下的类。由于根类加载器是由JVM实现的,无法通过Java代码直接访问,因此可以防止恶意代码替换核心类库。

总结

  • 解决类加载的冲突和重复加载的问题
  • 沙箱安全机制:防止Java核心API库被随意篡改。通俗点说,当Java核心类库中的类(比如:java.lang.String)的类名与用户自定义类的类名相同有冲突时,Java不会加载用户自定义的类。

java中双亲委派机制如何实现的

在java中的类加载器模型图如上,分为三层。

BootstrapClassLoader: 启动类加载器,最上层类加载器为本地方法,主要加载 jre/lib/rt.jar 路径内类文件

ExtClassLoader:扩展类加载器 主要加载 jre/lib/ext/*.jar

AppClassLoader: 用来加载CLASS_PATH 下所有jar,即项目所有class

UserClassLoader: 自定义类加载器,该类加载器是否遵循类加载机制,或特殊操作可以自己实现,打破类加载机机制也由该类加载器实现。

源码解析

测试代码如下

​ 尝试用AppClassLoader 去加载 java.lang.String

1
2
3
4
5
6
7
8
9
10
11
12
public class Main {
public static void main(String[] args) throws ClassNotFoundException {

ClassLoader appClassLoader = Main.class.getClassLoader();
// AppClassLoader
System.out.println("当前类加载器为:" + appClassLoader);

Class<?> aClass = appClassLoader.loadClass("java.lang.String");
ClassLoader StringClassLoader = aClass.getClassLoader();
System.out.println("String类加载器为:" + StringClassLoader);
}
}

测试结果:

1
2
当前类加载器为:sun.misc.Launcher$AppClassLoader@14dad5dc
String类加载器为:null

解释:

因为String类是由引导类加载器加载的,这个类加载器是JVM的一部分,不是一个标准的Java类加载器。由引导类加载器加载的类不会有一个对应的类加载器实例,因此返回null

类加载机制关键方法为loadClass, 接下来进行刨析jkd源码,去探索jdk如何实现的双亲委派机制

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
static class AppClassLoader extends URLClassLoader {
// 获取当前项目lib 及本身项目jar包路径
final URLClassPath ucp = SharedSecrets.getJavaNetAccess().getURLClassPath(this);

public static ClassLoader getAppClassLoader(final ClassLoader var0) throws IOException {
final String var1 = System.getProperty("java.class.path");
final File[] var2 = var1 == null ? new File[0] : Launcher.getClassPath(var1);
return (ClassLoader)AccessController.doPrivileged(new PrivilegedAction<AppClassLoader>() {
public AppClassLoader run() {
URL[] var1x = var1 == null ? new URL[0] : Launcher.pathToURLs(var2);
return new AppClassLoader(var1x, var0);
}
});
}

AppClassLoader(URL[] var1, ClassLoader var2) {
super(var1, var2, Launcher.factory);
this.ucp.initLookupCache(this);
}

public Class<?> loadClass(String var1, boolean var2) throws ClassNotFoundException {
int var3 = var1.lastIndexOf(46);
if (var3 != -1) {
SecurityManager var4 = System.getSecurityManager();
if (var4 != null) {
var4.checkPackageAccess(var1.substring(0, var3));
}
}
// 判断当前项目 及 lib目录下有没有java.lang.String 显然没有
if (this.ucp.knownToNotExist(var1)) {
Class var5 = this.findLoadedClass(var1);
if (var5 != null) {
if (var2) {
this.resolveClass(var5);
}

return var5;
} else {
throw new ClassNotFoundException(var1);
}
} else {
// 调用父类loadClass, 父类loadClass 方法内就包含了双亲委派机制
return super.loadClass(var1, var2);
}
}

protected PermissionCollection getPermissions(CodeSource var1) {
PermissionCollection var2 = super.getPermissions(var1);
var2.add(new RuntimePermission("exitVM"));
return var2;
}

private void appendToClassPathForInstrumentation(String var1) {
assert Thread.holdsLock(this);

super.addURL(Launcher.getFileURL(new File(var1)));
}

private static AccessControlContext getContext(File[] var0) throws MalformedURLException {
PathPermissions var1 = new PathPermissions(var0);
ProtectionDomain var2 = new ProtectionDomain(new CodeSource(var1.getCodeBase(), (Certificate[])null), var1);
AccessControlContext var3 = new AccessControlContext(new ProtectionDomain[]{var2});
return var3;
}

static {
ClassLoader.registerAsParallelCapable();
}
}

父类方法如下

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
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
// 首先检查是否已经被本类加载器加载过了,findLoadedClass 本地方法不去关注
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
// 如果父类加载器不为空就调用 父类加载器的loadClass方法,即委托父类去加载
if (parent != null) {
c = parent.loadClass(name, false);
} else {
// 如果父类加载器为空就调用 BootstraploadClass方法,即委托Bootstrap类加载器去加载
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;
}
}

从上述源码显而易见,双亲委派机制本质上就是一个责任链, 每个子类加载器中都包含父类加载器, 一层层递归调用上去实现了双亲委派机制,既然了解了该机制那么破坏双亲委派机制就很简单了,只需要重写该方法时候不去调用parent.loadClass(name, false)即可。

为什么需要破坏双亲委派机制

目前我接触到的主要是为了解决类加载冲突:

例如:

1.在Tomcat中,不同的Web应用程序可能使用不同版本的jar包

2.javaAgent技术中引入三方依赖极易产生jar包冲突等现象,所以需要破坏双亲委派机制 例如sermant https://bbs.huaweicloud.com/blogs/410414 讲解很不错

如何破坏类加载机制

暂无