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可查看)
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类加载过程中有个双亲委派机制,加载某个类是会先委托其父加载器寻找目标类,找不到在层层委托上层父加载器加载,如果所以父加载器在自己的加载类路径下都找不到目标类,则会在自己的类加载路径中查找并载入目标类。
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去通过路径寻找需要加载的类。
全盘负责委托机制:
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类通过自己自定义的路径插入到文件夹中。
打破双亲委派机制:
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判断即可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix