从JDK源码级别彻底剖析JVM类加载机制
1.从java.exe 开始分析java类加载运行全过程
当我们用java命令运行某个类的main函数启动程序时,首先需要通过类加载器把主类加载到 JVM。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | package com.test.controller; public class Test { public static final int initData = 666 ; public static User user = new User(); public int compute() { //一个方法对应一块栈帧内存区域 int a = 1 ; int b = 2 ; int c = (a + b) * 10 ; return c; } public static void main(String[] args) { Test test = new Test(); test.compute(); } } |
通过Java命令执行代码的大体流程如下:
2.从JDK源码级别剖析JVM核心类加载器
其中loadClass的类加载过程有如下几步:
加载 >> 验证 >> 准备 >> 解析 >> 初始化 >> 使用 >> 卸载
- 加载:在硬盘上查找并通过IO读入字节码文件,使用到类时才会加载,例如调用类的 main()方法,new对象等等,在加载阶段会在内存中生成一个代表这个类的 java.lang.Class对象,作为方法区这个类的各种数据的访问入口
- 验证:校验字节码文件的正确性
- 准备:给类的静态变量分配内存,并赋予默认值
- 解析:将符号引用替换为直接引用,该阶段会把一些静态方法(符号引用,比如 main()方法)替换为指向数据所存内存的指针或句柄等(直接引用),这是所谓的静态链接过 程(类加载期间完成),动态链接是在程序运行期间完成的将符号引用替换为直接引用,下 节课会讲到动态链接
初始化:对类的静态变量初始化为指定的值,执行静态代码
类被加载到方法区中后主要包含 运行时常量池、类型信息、字段信息、方法信息、类加载器的引用、对应class实例的引用等信息。
类加载器的引用:这个类到类加载器实例的引用
对应class实例的引用:类加载器在加载类信息放到方法区中后,会创建一个对应的Class 类型的 对象实例放到堆(Heap)中, 作为开 发人员访问方法区中类定义的入口和切入点。
注意,主类在运行过程中如果使用到其它类,会逐步加载这些类。 jar包或war包里的类不是一次性全部加载的,是使用到时才加载。
3.从JDK源码级别剖析类加载双亲委派机制
类加载器和双亲委派机制
上面的类加载过程主要是通过类加载器来实现的,Java里有如下几种类加载器
- 引导类加载器:负责加载支撑JVM运行的位于JRE的lib目录下的核心类库,比如 rt.jar、charsets.jar等
- 扩展类加载器:负责加载支撑JVM运行的位于JRE的lib目录下的ext扩展目录中的JAR 类包
- 应用程序类加载器:负责加载ClassPath路径下的类包,主要就是加载你自己写的那 些类
- 自定义加载器:负责加载用户自定义路径下的类包
类加载器初始化过程:
参见类运行加载全过程图可知其中会创建JVM启动器实例sun.misc.Launcher。
sun.misc.Launcher初始化使用了单例模式设计,保证一个JVM虚拟机内只有一个 sun.misc.Launcher实例。
在Launcher构造方法内部,其创建了两个类加载器,分别是 sun.misc.Launcher.ExtClassLoader(扩展类加载器)和sun.misc.Launcher.AppClassLoader(应 用类加载器)。
JVM默认使用Launcher的getClassLoader()方法返回的类加载器AppClassLoader的实例加载我们 的应用程序。
1 public Launcher() { 2 Launcher.ExtClassLoader var1; 3 try {
//构造扩展类加载器,在构造的过程中将其父加载器设置为null 4 var1 = Launcher.ExtClassLoader.getExtClassLoader(); 5 } catch (IOException var10) { 6 throw new InternalError("Could not create extension class loader", var10); 7 } 8 9 try {
//构造应用类加载器,在构造的过程中将其父加载器设置为ExtClassLoader,
//Launcher的loader属性值是AppClassLoader,我们一般都是用这个类加载器来加载我们自 己写的应用程序
10 this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
11 } catch (IOException var9) { 12 throw new InternalError("Could not create application class loader", var9); 13 } 14 15 Thread.currentThread().setContextClassLoader(this.loader); 16 String var2 = System.getProperty("java.security.manager");
...
17 }
双亲委派机制
JVM类加载器是有亲子层级结构的,如下图
这里类加载其实就有一个双亲委派机制,加载某个类时会先委托父加载器寻找目标类,找不到再 委托上层父加载器加载,如果所有父加载器在自己的加载类路径下都找不到目标类,则在自己的 类加载路径中查找并载入目标类。
比如我们的Math类,最先会找应用程序类加载器加载,应用程序类加载器会先委托扩展类加载 器加载,扩展类加载器再委托引导类加载器,顶层引导类加载器在自己的类加载路径里找了半天 没找到Math类,则向下退回加载Math类的请求,扩展类加载器收到回复就自己加载,在自己的 类加载路径里找了半天也没找到Math类,又向下退回Math类的加载请求给应用程序类加载器, 应用程序类加载器于是在自己的类加载路径里找Math类,结果找到了就自己加载了。。
双亲委派机制说简单点就是,先找父亲加载,不行再由儿子自己加载
我们来看下应用程序类加载器AppClassLoader加载类的双亲委派机制源码,AppClassLoader 的loadClass方法最终会调用其父类ClassLoader的loadClass方法,该方法的大体逻辑如下:
- 首先,检查一下指定名称的类是否已经加载过,如果加载过了,就不需要再加载,直接 返回。
- 如果此类没有加载过,那么,再判断一下是否有父加载器;如果有父加载器,则由父加 载器加载(即调用parent.loadClass(name, false);).或者是调用bootstrap类加载器来加 载。
- 如果父加载器及bootstrap类加载器都没有找到指定的类,那么调用当前类加载器的 findClass方法来完成类加载。
1 //ClassLoader的loadClass方法,里面实现了双亲委派机制 2 protected Class<?> loadClass(String name, boolean resolve) 3 throws ClassNotFoundException 4 { 5 synchronized (getClassLoadingLock(name)) { 6 // 检查当前类加载器是否已经加载了该类 7 Class<?> c = findLoadedClass(name); 8 if (c == null) { 9 long t0 = System.nanoTime(); 10 try { 11 if (parent != null) { //如果当前加载器父加载器不为空则委托父加载器加载该类 12 c = parent.loadClass(name, false); 13 } else { //如果当前加载器父加载器为空则委托引导类加载器加载该类 14 c = findBootstrapClassOrNull(name); 15 } 16 } catch (ClassNotFoundException e) { 17 // ClassNotFoundException thrown if class not found 18 // from the non‐null parent class loader 19 } 20 21 if (c == null) { 22 // If still not found, then invoke findClass in order 23 // to find the class. 24 long t1 = System.nanoTime(); 25 //都会调用URLClassLoader的findClass方法在加载器的类路径里查找并加载该类 26 c = findClass(name); 27 28 // this is the defining class loader; record the stats 29 sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 ‐ t0); 30 sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); 31 sun.misc.PerfCounter.getFindClasses().increment(); 32 } 33 } 34 if (resolve) { //不会执行 35 resolveClass(c); 36 } 37 return c; 38 } 39 }
为什么要设计双亲委派机制?
- 沙箱安全机制:自己写的java.lang.String.class类不会被加载,这样便可以防止核心 API库被随意篡改
- 避免类的重复加载:当父亲已经加载了该类时,就没有必要子ClassLoader再加载一 次,保证被加载类的唯一性
看一个类加载示例:
1 2 3 4 5 6 7 8 9 10 11 12 | 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 |
全盘负责委托机制
“全盘负责”是指当一个ClassLoder装载一个类时,除非显示的使用另外一个ClassLoder,该类 所依赖及引用的类也由这个ClassLoder载入。
4.手写自定义类加载打破双亲委派机制
自定义类加载器示例: 自定义类加载器只需要继承 java.lang.ClassLoader 类,该类有两个核心方法,一个是 loadClass(String, boolean),实现了双亲委派机制,还有一个方法是findClass,默认实现是空 方法,所以我们自定义类加载器主要是重写findClass方法。
1 public class MyClassLoaderTest { 2 static class MyClassLoader extends ClassLoader { 3 private String classPath; 4 5 public MyClassLoader(String classPath) { 6 this.classPath = classPath; 7 } 8 9 private byte[] loadByte(String name) throws Exception { 10 name = name.replaceAll("\\.", "/"); 11 FileInputStream fis = new FileInputStream(classPath + "/" + name 12 + ".class"); 13 int len = fis.available(); 14 byte[] data = new byte[len]; 15 fis.read(data); 16 fis.close(); 17 return data; 18 } 19 20 protected Class<?> findClass(String name) throws ClassNotFoundException { 21 try { 22 byte[] data = loadByte(name); 23 //defineClass将一个字节数组转为Class对象,这个字节数组是class文件读取后最终的字节 数组。 24 return defineClass(name, data, 0, data.length); 25 } catch (Exception e) { 26 e.printStackTrace(); 27 throw new ClassNotFoundException(); 28 } 29 } 30 31 } 32 33 public static void main(String args[]) throws Exception { 34 //初始化自定义类加载器,会先初始化父类ClassLoader,其中会把自定义类加载器的父加载 器设置为应用程序类加载器AppClassLoader 35 MyClassLoader classLoader = new MyClassLoader("D:/test"); 36 //D盘创建 test/com/tuling/jvm 几级目录,将User类的复制类User1.class丢入该目录 37 Class clazz = classLoader.loadClass("com.tuling.jvm.User1"); 38 Object obj = clazz.newInstance(); 39 Method method = clazz.getDeclaredMethod("sout", null); 40 method.invoke(obj, null); 41 System.out.println(clazz.getClassLoader().getClass().getName()); 42 } 43 } 44 45 运行结果: 46 =======自己的加载器加载类调用方法======= 47 com.tuling.jvm.MyClassLoaderTest$MyClassLoader
打破双亲委派机制
再来一个沙箱安全机制示例,尝试打破双亲委派机制,用自定义类加载器加载我们自己实现的
java.lang.String.class
public class MyClassLoaderTest { 2 static class MyClassLoader extends ClassLoader { 3 private String classPath; 4 5 public MyClassLoader(String classPath) { 6 this.classPath = classPath; 7 } 8 9 private byte[] loadByte(String name) throws Exception { 10 name = name.replaceAll("\\.", "/"); 11 FileInputStream fis = new FileInputStream(classPath + "/" + name 12 + ".class"); 13 int len = fis.available(); 14 byte[] data = new byte[len]; 15 fis.read(data); 16 fis.close(); 17 return data; 18 19 } 20 21 protected Class<?> findClass(String name) throws ClassNotFoundException { 22 try { 23 byte[] data = loadByte(name); 24 return defineClass(name, data, 0, data.length); 25 } catch (Exception e) { 26 e.printStackTrace(); 27 throw new ClassNotFoundException(); 28 } 29 } 30 31 /** 32 * 重写类加载方法,实现自己的加载逻辑,不委派给双亲加载 33 * @param name 34 * @param resolve 35 * @return 36 * @throws ClassNotFoundException 37 */ 38 protected Class<?> loadClass(String name, boolean resolve) 39 throws ClassNotFoundException { 40 synchronized (getClassLoadingLock(name)) { 41 // First, check if the class has already been loaded 42 Class<?> c = findLoadedClass(name); 43 44 if (c == null) { 45 // If still not found, then invoke findClass in order 46 // to find the class. 47 long t1 = System.nanoTime(); 48 c = findClass(name); 49 50 // this is the defining class loader; record the stats 51 sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); 52 sun.misc.PerfCounter.getFindClasses().increment(); 53 } 54 if (resolve) { 55 resolveClass(c); 56 } 57 return c; 58 } 59 } 60 } 61 62 public static void main(String args[]) throws Exception { 63 MyClassLoader classLoader = new MyClassLoader("D:/test"); 64 //尝试用自己改写类加载机制去加载自己写的java.lang.String.class 65 Class clazz = classLoader.loadClass("java.lang.String"); 66 Object obj = clazz.newInstance(); 67 Method method= clazz.getDeclaredMethod("sout", null); 68 method.invoke(obj, null); 69 System.out.println(clazz.getClassLoader().getClass().getName()); 70 } 71 } 72 73 运行结果: 74 java.lang.SecurityException: Prohibited package name: java.lang 75 at java.lang.ClassLoader.preDefineClass(ClassLoader.java:659) 76 at java.lang.ClassLoader.defineClass(ClassLoader.java:758)
5.tomcat类加载机制深度剖析
6.手写tomcat类加载器实现多版本代码共存隔离
------------恢复内容结束------------
------------恢复内容开始------------
1.从java.exe 开始分析java类加载运行全过程
当我们用java命令运行某个类的main函数启动程序时,首先需要通过类加载器把主类加载到 JVM。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | package com.test.controller; public class Test { public static final int initData = 666 ; public static User user = new User(); public int compute() { //一个方法对应一块栈帧内存区域 int a = 1 ; int b = 2 ; int c = (a + b) * 10 ; return c; } public static void main(String[] args) { Test test = new Test(); test.compute(); } } |
通过Java命令执行代码的大体流程如下:
2.从JDK源码级别剖析JVM核心类加载器
其中loadClass的类加载过程有如下几步:
加载 >> 验证 >> 准备 >> 解析 >> 初始化 >> 使用 >> 卸载
- 加载:在硬盘上查找并通过IO读入字节码文件,使用到类时才会加载,例如调用类的 main()方法,new对象等等,在加载阶段会在内存中生成一个代表这个类的 java.lang.Class对象,作为方法区这个类的各种数据的访问入口
- 验证:校验字节码文件的正确性
- 准备:给类的静态变量分配内存,并赋予默认值
- 解析:将符号引用替换为直接引用,该阶段会把一些静态方法(符号引用,比如 main()方法)替换为指向数据所存内存的指针或句柄等(直接引用),这是所谓的静态链接过 程(类加载期间完成),动态链接是在程序运行期间完成的将符号引用替换为直接引用,下 节课会讲到动态链接
初始化:对类的静态变量初始化为指定的值,执行静态代码
类被加载到方法区中后主要包含 运行时常量池、类型信息、字段信息、方法信息、类加载器的引用、对应class实例的引用等信息。
类加载器的引用:这个类到类加载器实例的引用
对应class实例的引用:类加载器在加载类信息放到方法区中后,会创建一个对应的Class 类型的 对象实例放到堆(Heap)中, 作为开 发人员访问方法区中类定义的入口和切入点。
注意,主类在运行过程中如果使用到其它类,会逐步加载这些类。 jar包或war包里的类不是一次性全部加载的,是使用到时才加载。
3.从JDK源码级别剖析类加载双亲委派机制
类加载器和双亲委派机制
上面的类加载过程主要是通过类加载器来实现的,Java里有如下几种类加载器
- 引导类加载器:负责加载支撑JVM运行的位于JRE的lib目录下的核心类库,比如 rt.jar、charsets.jar等
- 扩展类加载器:负责加载支撑JVM运行的位于JRE的lib目录下的ext扩展目录中的JAR 类包
- 应用程序类加载器:负责加载ClassPath路径下的类包,主要就是加载你自己写的那 些类
- 自定义加载器:负责加载用户自定义路径下的类包
类加载器初始化过程:
参见类运行加载全过程图可知其中会创建JVM启动器实例sun.misc.Launcher。
sun.misc.Launcher初始化使用了单例模式设计,保证一个JVM虚拟机内只有一个 sun.misc.Launcher实例。
在Launcher构造方法内部,其创建了两个类加载器,分别是 sun.misc.Launcher.ExtClassLoader(扩展类加载器)和sun.misc.Launcher.AppClassLoader(应 用类加载器)。
JVM默认使用Launcher的getClassLoader()方法返回的类加载器AppClassLoader的实例加载我们 的应用程序。
1 public Launcher() { 2 Launcher.ExtClassLoader var1; 3 try {
//构造扩展类加载器,在构造的过程中将其父加载器设置为null 4 var1 = Launcher.ExtClassLoader.getExtClassLoader(); 5 } catch (IOException var10) { 6 throw new InternalError("Could not create extension class loader", var10); 7 } 8 9 try {
//构造应用类加载器,在构造的过程中将其父加载器设置为ExtClassLoader,
//Launcher的loader属性值是AppClassLoader,我们一般都是用这个类加载器来加载我们自 己写的应用程序
10 this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
11 } catch (IOException var9) { 12 throw new InternalError("Could not create application class loader", var9); 13 } 14 15 Thread.currentThread().setContextClassLoader(this.loader); 16 String var2 = System.getProperty("java.security.manager");
...
17 }
双亲委派机制
JVM类加载器是有亲子层级结构的,如下图
这里类加载其实就有一个双亲委派机制,加载某个类时会先委托父加载器寻找目标类,找不到再 委托上层父加载器加载,如果所有父加载器在自己的加载类路径下都找不到目标类,则在自己的 类加载路径中查找并载入目标类。
比如我们的Math类,最先会找应用程序类加载器加载,应用程序类加载器会先委托扩展类加载 器加载,扩展类加载器再委托引导类加载器,顶层引导类加载器在自己的类加载路径里找了半天 没找到Math类,则向下退回加载Math类的请求,扩展类加载器收到回复就自己加载,在自己的 类加载路径里找了半天也没找到Math类,又向下退回Math类的加载请求给应用程序类加载器, 应用程序类加载器于是在自己的类加载路径里找Math类,结果找到了就自己加载了。。
双亲委派机制说简单点就是,先找父亲加载,不行再由儿子自己加载
我们来看下应用程序类加载器AppClassLoader加载类的双亲委派机制源码,AppClassLoader 的loadClass方法最终会调用其父类ClassLoader的loadClass方法,该方法的大体逻辑如下:
- 首先,检查一下指定名称的类是否已经加载过,如果加载过了,就不需要再加载,直接 返回。
- 如果此类没有加载过,那么,再判断一下是否有父加载器;如果有父加载器,则由父加 载器加载(即调用parent.loadClass(name, false);).或者是调用bootstrap类加载器来加 载。
- 如果父加载器及bootstrap类加载器都没有找到指定的类,那么调用当前类加载器的 findClass方法来完成类加载。
1 //ClassLoader的loadClass方法,里面实现了双亲委派机制 2 protected Class<?> loadClass(String name, boolean resolve) 3 throws ClassNotFoundException 4 { 5 synchronized (getClassLoadingLock(name)) { 6 // 检查当前类加载器是否已经加载了该类 7 Class<?> c = findLoadedClass(name); 8 if (c == null) { 9 long t0 = System.nanoTime(); 10 try { 11 if (parent != null) { //如果当前加载器父加载器不为空则委托父加载器加载该类 12 c = parent.loadClass(name, false); 13 } else { //如果当前加载器父加载器为空则委托引导类加载器加载该类 14 c = findBootstrapClassOrNull(name); 15 } 16 } catch (ClassNotFoundException e) { 17 // ClassNotFoundException thrown if class not found 18 // from the non‐null parent class loader 19 } 20 21 if (c == null) { 22 // If still not found, then invoke findClass in order 23 // to find the class. 24 long t1 = System.nanoTime(); 25 //都会调用URLClassLoader的findClass方法在加载器的类路径里查找并加载该类 26 c = findClass(name); 27 28 // this is the defining class loader; record the stats 29 sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 ‐ t0); 30 sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); 31 sun.misc.PerfCounter.getFindClasses().increment(); 32 } 33 } 34 if (resolve) { //不会执行 35 resolveClass(c); 36 } 37 return c; 38 } 39 }
为什么要设计双亲委派机制?
- 沙箱安全机制:自己写的java.lang.String.class类不会被加载,这样便可以防止核心 API库被随意篡改
- 避免类的重复加载:当父亲已经加载了该类时,就没有必要子ClassLoader再加载一 次,保证被加载类的唯一性
看一个类加载示例:
1 2 3 4 5 6 7 8 9 10 11 12 | 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 |
全盘负责委托机制
“全盘负责”是指当一个ClassLoder装载一个类时,除非显示的使用另外一个ClassLoder,该类 所依赖及引用的类也由这个ClassLoder载入。
4.手写自定义类加载打破双亲委派机制
自定义类加载器示例: 自定义类加载器只需要继承 java.lang.ClassLoader 类,该类有两个核心方法,一个是 loadClass(String, boolean),实现了双亲委派机制,还有一个方法是findClass,默认实现是空 方法,所以我们自定义类加载器主要是重写findClass方法。
1 public class MyClassLoaderTest { 2 static class MyClassLoader extends ClassLoader { 3 private String classPath; 4 5 public MyClassLoader(String classPath) { 6 this.classPath = classPath; 7 } 8 9 private byte[] loadByte(String name) throws Exception { 10 name = name.replaceAll("\\.", "/"); 11 FileInputStream fis = new FileInputStream(classPath + "/" + name 12 + ".class"); 13 int len = fis.available(); 14 byte[] data = new byte[len]; 15 fis.read(data); 16 fis.close(); 17 return data; 18 } 19 20 protected Class<?> findClass(String name) throws ClassNotFoundException { 21 try { 22 byte[] data = loadByte(name); 23 //defineClass将一个字节数组转为Class对象,这个字节数组是class文件读取后最终的字节 数组。 24 return defineClass(name, data, 0, data.length); 25 } catch (Exception e) { 26 e.printStackTrace(); 27 throw new ClassNotFoundException(); 28 } 29 } 30 31 } 32 33 public static void main(String args[]) throws Exception { 34 //初始化自定义类加载器,会先初始化父类ClassLoader,其中会把自定义类加载器的父加载 器设置为应用程序类加载器AppClassLoader 35 MyClassLoader classLoader = new MyClassLoader("D:/test"); 36 //D盘创建 test/com/tuling/jvm 几级目录,将User类的复制类User1.class丢入该目录 37 Class clazz = classLoader.loadClass("com.tuling.jvm.User1"); 38 Object obj = clazz.newInstance(); 39 Method method = clazz.getDeclaredMethod("sout", null); 40 method.invoke(obj, null); 41 System.out.println(clazz.getClassLoader().getClass().getName()); 42 } 43 } 44 45 运行结果: 46 =======自己的加载器加载类调用方法======= 47 com.tuling.jvm.MyClassLoaderTest$MyClassLoader
打破双亲委派机制
再来一个沙箱安全机制示例,尝试打破双亲委派机制,用自定义类加载器加载我们自己实现的
java.lang.String.class
public class MyClassLoaderTest { 2 static class MyClassLoader extends ClassLoader { 3 private String classPath; 4 5 public MyClassLoader(String classPath) { 6 this.classPath = classPath; 7 } 8 9 private byte[] loadByte(String name) throws Exception { 10 name = name.replaceAll("\\.", "/"); 11 FileInputStream fis = new FileInputStream(classPath + "/" + name 12 + ".class"); 13 int len = fis.available(); 14 byte[] data = new byte[len]; 15 fis.read(data); 16 fis.close(); 17 return data; 18 19 } 20 21 protected Class<?> findClass(String name) throws ClassNotFoundException { 22 try { 23 byte[] data = loadByte(name); 24 return defineClass(name, data, 0, data.length); 25 } catch (Exception e) { 26 e.printStackTrace(); 27 throw new ClassNotFoundException(); 28 } 29 } 30 31 /** 32 * 重写类加载方法,实现自己的加载逻辑,不委派给双亲加载 33 * @param name 34 * @param resolve 35 * @return 36 * @throws ClassNotFoundException 37 */ 38 protected Class<?> loadClass(String name, boolean resolve) 39 throws ClassNotFoundException { 40 synchronized (getClassLoadingLock(name)) { 41 // First, check if the class has already been loaded 42 Class<?> c = findLoadedClass(name); 43 44 if (c == null) { 45 // If still not found, then invoke findClass in order 46 // to find the class. 47 long t1 = System.nanoTime(); 48 c = findClass(name); 49 50 // this is the defining class loader; record the stats 51 sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); 52 sun.misc.PerfCounter.getFindClasses().increment(); 53 } 54 if (resolve) { 55 resolveClass(c); 56 } 57 return c; 58 } 59 } 60 } 61 62 public static void main(String args[]) throws Exception { 63 MyClassLoader classLoader = new MyClassLoader("D:/test"); 64 //尝试用自己改写类加载机制去加载自己写的java.lang.String.class 65 Class clazz = classLoader.loadClass("java.lang.String"); 66 Object obj = clazz.newInstance(); 67 Method method= clazz.getDeclaredMethod("sout", null); 68 method.invoke(obj, null); 69 System.out.println(clazz.getClassLoader().getClass().getName()); 70 } 71 } 72 73 运行结果: 74 java.lang.SecurityException: Prohibited package name: java.lang 75 at java.lang.ClassLoader.preDefineClass(ClassLoader.java:659) 76 at java.lang.ClassLoader.defineClass(ClassLoader.java:758)
5.tomcat类加载机制深度剖析
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(1)