为什么需要双亲委派机制 双亲委派机制 是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(); 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 { 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)); } } 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 { 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)) { Class<?> c = findLoadedClass(name); if (c == null ) { long t0 = System.nanoTime(); try { if (parent != null ) { c = parent.loadClass(name, false ); } else { c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { } if (c == null ) { long t1 = System.nanoTime(); c = findClass(name); 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 讲解很不错
如何破坏类加载机制 暂无