JVM的类加载器和双亲委派机制

Java的类加载过程主要是通过类加载器来实现的,其中Java有如下几种加载器

1:引导类加载器:负责支撑JVM运行的位于我们JDK中的JRE目录的lib目录下的核心类库。

 

 

扩展类加载器:负责支撑JVM运行的位于JDK中的JRE目录的lib目录下的ext扩展目录中的JAR类包

应用程序类加载器:负责我们项目本地中的ClassPath路径下的类包,主要就是加载你自己写的那些类的Class字节码文件的

自定义加载器:负责加载我们自定义路径下的的类包。

 

例:一个类加载器实例:

复制代码
 1 public class TestClassLoader {
 2 
 3     public static void main(String[] args) {
 4         System.out.println(String.class.getClassLoader());
 5         System.out.println(com.sun.crypto.provider.DESKeyFactory.class.getClassLoader().getClass().getName());
 6         System.out.println(TestClassLoader.class.getClassLoader().getClass().getName());
 7 
 8         System.out.println();
 9         ClassLoader appClassLoader = ClassLoader.getSystemClassLoader();
10         ClassLoader extClassloader = appClassLoader.getParent();
11         ClassLoader bootstrapLoader = extClassloader.getParent();
12         System.out.println("the bootstrapLoader : " + bootstrapLoader);
13         System.out.println("the extClassloader : " + extClassloader);
14         System.out.println("the appClassLoader : " + appClassLoader);
15 
16         System.out.println();
17         System.out.println("bootstrapLoader加载以下文件:");
18         URL[] urls = Launcher.getBootstrapClassPath().getURLs();
19         for (int i = 0; i < urls.length; i++) {
20             System.out.println(urls[i]);
21         }
22 
23         System.out.println();
24         System.out.println("extClassloader加载以下文件:");
25         System.out.println(System.getProperty("java.ext.dirs"));
26 
27         System.out.println();
28         System.out.println("appClassLoader加载以下文件:");
29         System.out.println(System.getProperty("java.class.path"));
30 
31     }
32 }
运行结果:

null
sun.misc.Launcher$ExtClassLoader
sun.misc.Launcher$AppClassLoader

the bootstrapLoader : null
the extClassloader : sun.misc.Launcher$ExtClassLoader@38af3868
the appClassLoader : sun.misc.Launcher$AppClassLoader@18b4aac2

bootstrapLoader加载以下文件:
file:/C:/Program%20Files/Java/jdk1.8.0_291/jre/lib/resources.jar
file:/C:/Program%20Files/Java/jdk1.8.0_291/jre/lib/rt.jar
file:/C:/Program%20Files/Java/jdk1.8.0_291/jre/lib/sunrsasign.jar
file:/C:/Program%20Files/Java/jdk1.8.0_291/jre/lib/jsse.jar
file:/C:/Program%20Files/Java/jdk1.8.0_291/jre/lib/jce.jar
file:/C:/Program%20Files/Java/jdk1.8.0_291/jre/lib/charsets.jar
file:/C:/Program%20Files/Java/jdk1.8.0_291/jre/lib/jfr.jar
file:/C:/Program%20Files/Java/jdk1.8.0_291/jre/classes

extClassloader加载以下文件:
C:\Program Files\Java\jdk1.8.0_291\jre\lib\ext;C:\WINDOWS\Sun\Java\lib\ext

appClassLoader加载以下文件:
C:\Program Files\Java\jdk1.8.0_291\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_291\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_291\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_291\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_291\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_291\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_291\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_291\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_291\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_291\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_291\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_291\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_291\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_291\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_291\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_291\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_291\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_291\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_291\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_291\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_291\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_291\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_291\jre\lib\rt.jar;D:\spring\JVMDemo\target\classes;D:\Program Files\JetBrains\IntelliJ IDEA 2020.3.2\lib\idea_rt.jar

复制代码

类加载器的初始化过程:

类加载器加载全过程可知会创建JVM启动器实例sun.misc.Launcher。(JDK源码中sun.misc.Launcher可查看)

在Launcher构造方法内部,其创建了两个类加载器,分别是sun.misc.Launcher.ExtClassLoader(扩展类加载器)和sun.misc.Launcher.AppClassLoader(应用类加载器)。
JVM默认使用Launcher的getClassLoader()方法返回的类加载器AppClassLoader的实例加载我们的应用程序。

JDK源码中的Launcher:如下:

复制代码
 1 //Launcher的构造方法
 2 public Launcher() {
 3     Launcher.ExtClassLoader var1;
 4     try {
 5         //构造扩展类加载器,在构造的过程中将其父加载器设置为null
 6         var1 = Launcher.ExtClassLoader.getExtClassLoader();
 7     } catch (IOException var10) {
 8         throw new InternalError("Could not create extension class loader", var10);
 9     }
10 
11     try {
12         //构造应用类加载器,在构造的过程中将其父加载器设置为ExtClassLoader,
13         //Launcher的loader属性值是AppClassLoader,我们一般都是用这个类加载器来加载我们自己写的应用程序
14         this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
15     } catch (IOException var9) {
16         throw new InternalError("Could not create application class loader", var9);
17     }
18 
19     Thread.currentThread().setContextClassLoader(this.loader);
20     String var2 = System.getProperty("java.security.manager");
21     。。。 。。。 //省略一些不需关注代码
22 
23 }
复制代码

类加载过程中常见的:双亲委派机制:JVM类加载器是有亲子层结构的,如下图:

 

 如图所示在JVM类加载过程中有个双亲委派机制,加载某个类是会先委托其父加载器寻找目标类,找不到在层层委托上层父加载器加载,如果所以父加载器在自己的加载类路径下都找不到目标类,则会在自己的类加载路径中查找并载入目标类。

比如我们的Math类,最先会找应用程序类加载器加载,应用程序类加载器会先委托扩展类加载器加载,扩展类加载器再委托引导类加载器,顶层引导类加载器在自己的类加载路径里找了半天没找到Math类,则向下退回加载Math类的请求,扩展类加载器收到回复就自己加载,在自己的类加载路径里找了半天也没找到Math类,又向下退回Math类的加载请求给应用程序类加载器,应用程序类加载器于是在自己的类加载路径里找Math类,结果找到了就自己加载了。。
双亲委派机制简单点就是:先找父亲加载,不行再由儿子自己加载
 
那我们为什么会设计双亲委派机制呢?
1:其中JDK有个沙箱安全机制:就是JDK自己写的java.lang.string.class类不会被加载,这样是为了防止核心API库被随意篡改。
2:避免类的重复加载:当父亲已经加载了该类时,就没必要让子ClassLoader再加载一次了,保证被加载类的唯一性
 
如下实例代码:
复制代码
 1 package java.lang;
 2 
 3 public class String {
 4     public static void main(String[] args) {
 5         System.out.println("**************My String Class**************");
 6     }
 7 }
 8 
 9 运行结果:
10 错误: 在类 java.lang.String 中找不到 main 方法, 请将 main 方法定义为:
11    public static void main(String[] args)
12 否则 JavaFX 应用程序类必须扩展javafx.application.Application
复制代码

上述代码就违法了JDK的沙箱安全机制,自己手写的java.lang.string.class跟JDK的string路径一致,违法了JDK的沙箱安全机制。

总结:JDK中的类加载器实现就是通过LoadClass中的findClass去通过路径寻找需要加载的类。

全盘负责委托机制:

“全盘负责”是指当一个ClassLoder装载一个类时,除非显示的使用另外一个ClassLoder,该类所依赖及引用的类也由这个ClassLoder载入。
 
自定义类加载器实例:
自定义类加载器只需要继承 java.lang.ClassLoader 类,该类有两个核心方法,一个是loadClass(String, boolean),实现了双亲委派机制,还有一个方法是findClass,默认实现是空方法,所以我们自定义类加载器主要是重写findClass方法。
复制代码
 1 package com.hu.jvm;
 2 
 3 import java.io.FileInputStream;
 4 import java.lang.reflect.Method;
 5 
 6 /**
 7  * @author hu
 8  * @ClassName: $
 9  * @Description: $
10  * @Date $
11  * @Version 1.0
12  */
13 public class MyClassLoaderTest extends ClassLoader{
14 
15 
16     static class MyClassLoader extends ClassLoader {
17         private String classPath;
18 
19         public MyClassLoader(String classPath) {
20             this.classPath = classPath;
21         }
22 
23         private byte[] loadByte(String name) throws Exception {
24             name = name.replaceAll("\\.", "/");
25             FileInputStream fis = new FileInputStream(classPath + "/" + name
26                     + ".class");
27             int len = fis.available();
28             byte[] data = new byte[len];
29             fis.read(data);
30             fis.close();
31             return data;
32         }
33 
34         @Override
35         protected Class<?> findClass(String name) throws ClassNotFoundException {
36             try {
37                 byte[] data = loadByte(name);
38                 //defineClass将一个字节数组转为Class对象,这个字节数组是class文件读取后最终的字节数组。
39                 return defineClass(name, data, 0, data.length);
40             } catch (Exception e) {
41                 e.printStackTrace();
42                 throw new ClassNotFoundException();
43             }
44         }
45     }
46 
47     public static void main(String args[]) throws Exception {
48         //初始化自定义类加载器,会先初始化父类ClassLoader,其中会把自定义类加载器的父加载器设置为应用程序类加载器AppClassLoader
49         MyClassLoader classLoader = new MyClassLoader("D:/test");
50         //D盘创建 test/com/tuling/jvm 几级目录,将User类的复制类User1.class丢入该目录
51         Class clazz = classLoader.loadClass("com.hu.jvm.User1");
52         Object obj = clazz.newInstance();
53         Method method = clazz.getDeclaredMethod("sout", null);
54         method.invoke(obj, null);
55         System.out.println(clazz.getClassLoader().getClass().getName());
56     }
57 }
58 
59 运行结果:
60 =======自己的加载器加载类调用方法=======
61 com.hu.jvm.MyClassLoaderTest$MyClassLoader
复制代码

其中需要在磁盘D盘中建: D:/test文件夹,再将目标User1类通过自己自定义的路径插入到文件夹中。

 

打破双亲委派机制:

再来一个沙箱安全机制示例,尝试打破双亲委派机制,用自定义类加载器加载我们自己实现的 java.lang.String.class
复制代码
 1 package com.hu.jvm;
 2 
 3 import java.io.FileInputStream;
 4 import java.lang.reflect.Method;
 5 
 6 /**
 7  * @author hu
 8  * @ClassName: $
 9  * @Description: $
10  * @Date $
11  * @Version 1.0
12  */
13 public class MyClassLoaderDissTest {
14     static class MyClassLoader extends ClassLoader {
15         private String classPath;
16 
17         public MyClassLoader(String classPath) {
18             this.classPath = classPath;
19         }
20 
21         private byte[] loadByte(String name) throws Exception {
22             name = name.replaceAll("\\.", "/");
23             FileInputStream fis = new FileInputStream(classPath + "/" + name
24                     + ".class");
25             int len = fis.available();
26             byte[] data = new byte[len];
27             fis.read(data);
28             fis.close();
29             return data;
30 
31         }
32 
33         @Override
34         protected Class<?> findClass(String name) throws ClassNotFoundException {
35             try {
36                 byte[] data = loadByte(name);
37                 return defineClass(name, data, 0, data.length);
38             } catch (Exception e) {
39                 e.printStackTrace();
40                 throw new ClassNotFoundException();
41             }
42         }
43 
44         /**
45          * 重写类加载方法,实现自己的加载逻辑,不委派给双亲加载
46          * @param name
47          * @param resolve
48          * @return
49          * @throws ClassNotFoundException
50          */
51         @Override
52         protected Class<?> loadClass(String name, boolean resolve)
53                 throws ClassNotFoundException {
54             synchronized (getClassLoadingLock(name)) {
55                 // First, check if the class has already been loaded
56                 Class<?> c = findLoadedClass(name);
57 
58                 if (c == null) {
59                     // If still not found, then invoke findClass in order
60                     // to find the class.
61                     long t1 = System.nanoTime();
62                     c = findClass(name);
63 
64                     // this is the defining class loader; record the stats
65                     sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
66                     sun.misc.PerfCounter.getFindClasses().increment();
67                 }
68                 if (resolve) {
69                     resolveClass(c);
70                 }
71                 return c;
72             }
73         }
74     }
75 
76     public static void main(String args[]) throws Exception {
77         MyClassLoader classLoader = new MyClassLoader("D:/test");
78         //尝试用自己改写类加载机制去加载自己写的java.lang.String.class
79         Class clazz = classLoader.loadClass("java.lang.String");
80         Object obj = clazz.newInstance();
81         Method method= clazz.getDeclaredMethod("sout", null);
82         method.invoke(obj, null);
83         System.out.println(clazz.getClassLoader().getClass().getName());
84     }
85 }
86 
87 运行结果:
88 java.lang.SecurityException: Prohibited package name: java.lang
89     at java.lang.ClassLoader.preDefineClass(ClassLoader.java:659)
90     at java.lang.ClassLoader.defineClass(ClassLoader.java:758)
复制代码

报错的原因是打破了双亲委派机制之后没有了引导类加载器就无法去加载到JDK自带的一些JAR包,这里是加载不到java.lang.object包找不到

解决方法直接在loadClass中加个if判断即可。

posted @   LeslieBlogs  阅读(60)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
点击右上角即可分享
微信分享提示