JVM类加载机制 类加载运行全过程 当用java命令运行某个类的main函数启动程序时,首先需要通过类加载器 把主类加载到JVM。
package ink.chensir.jvm;public class SayHello { public static final String HELLO_STR ="Hello Jvm !" ; public void helloJvm () { System.out.println(HELLO_STR); } public static void main (String[] args) { SayHello sayHello = new SayHello(); sayHello.helloJvm(); } }
通过java命令执行代码的大体流程如下:
loadClass的类加载过程
加载 >> 验证 >> 准备 >> 解析 >> 初始化 >> 使用 >> 卸载
加载: 在硬盘上查找并通过IO读入字节码文件,使用到类的时候才会加载,例如调用类的main()方法,new对象等等,在加载阶段会在内存中生成一个代表这个类的 java.lang.Class对象 ,作为方法区这个类的各种数据访问入口
验证: 校验字节码文件的正确性
准备: 给类的静态变量分配内存,并赋予默认值
解析: 把静态方法的 符号 (变量名)引用 替换为 直接引用(内存地址) => **静态链接 ** ; 动态链接 (程序运行期间 符号引用 替换为 直接引用);
初始化: 对类的静态变量初始化为指定的值,执行静态代码块
类被加载到方法区中后主要包含运行时常量池、类型信息、字段信息、方法信息、类加载器的引用、对应class实例的引用 等信息。
类加载器的引用: 这个类到类加载器实例的引用
对应class实例的引用: 类加载器在加载类信息放到方法区中后,会创建一个对应的class类型的对象实例放到堆(Heap)中,作为开发人员访问方法区中类定义的入口和切入点。
注意, 主类在运行过程中如果使用其他类 ,会逐步加载这些类。jar包 或war包 里的类不是一次性全部加载的 ,是使用到时才加载 。
package ink.chensir.jvm;public class A { static { System.out.println("************* load A ************" ); } public A () { System.out.println("************* initial A ************" ); } } ======================================================================================== package ink.chensir.jvm;public class B { static { System.out.println("************* load B ************" ); } public B () { System.out.println("************* initial B ************" ); } } ======================================================================================== package ink.chensir.jvm;public class TestDynamicLoad { static { System.out.println("************* load TestDynamicLoad ************" ); } public static void main (String[] args) { new A(); System.out.println("************* load test ************" ); B b = null ; } } ======================================================================================== 运行结果: ************* load TestDynamicLoad ********** ************* load A ************************ ************* initial A ********************* ************* load test *********************
类加载器和双亲委派机制 上面的类加载过程主要是通过类加载器来实现的,java里有如下几种类加载器
Java类加载器
引导类加载器: 负责加载支撑JVM运行的位于JRE的lib目录下的核心类库,比如 rt.jar、charsets.jar等
扩展类加载器: 负责加载支撑JVM运行的位于JRElib目录下的ext扩展目录中的jar类包
应用程序类加载器: 负责加载ClassPath路径下的类包,主要就是加载你自己写的那些类
自定义类加载器: 负责加载用户自定义路径下的类包
类加载器示例 package ink.chensir.jvm;import sun.misc.Launcher;import java.net.URL;public class TestJDKClassLoader { public static void main (String[] args) { System.out.println(String.class.getClassLoader()); System.out.println(com.sun.crypto.provider.DESKeyFactory.class.getClassLoader().getClass().getName()); System.out.println(TestJDKClassLoader.class.getClassLoader().getClass().getName()); System.out.println("===========================================" ); ClassLoader appClassLoader = ClassLoader.getSystemClassLoader(); ClassLoader extClassLoader = appClassLoader.getParent(); ClassLoader bootStrapClassLoader = extClassLoader.getParent(); System.out.println("引导类加载器:" +bootStrapClassLoader); System.out.println("扩展类加载器:" +extClassLoader); System.out.println("应用类加载器:" +appClassLoader); System.out.println("===========================================" ); System.out.println("引导类加载器,加载以下文件:" ); URL[] urLs = Launcher.getBootstrapClassPath().getURLs(); for (URL urL : urLs) { System.out.println(urL); } System.out.println("扩展类加载器,加载以下文件:" ); System.out.println(System.getProperty("java.ext.dirs" )); System.out.println("===========================================" ); System.out.println("应用类加载器,加载以下文件:" ); System.out.println(System.getProperty("java.class.path" )); } } 运行结果: null sun.misc.Launcher$ExtClassLoader sun.misc.Launcher$AppClassLoader =========================================== 引导类加载器:null 扩展类加载器:sun.misc.Launcher$ExtClassLoader@5b480cf9 应用类加载器:sun.misc.Launcher$AppClassLoader@18b4aac2 =========================================== 引导类加载器,加载以下文件: file:/Library/Java/JavaVirtualMachines/jdk1.8 .0_311 .jdk/Contents/Home/jre/lib/resources.jar file:/Library/Java/JavaVirtualMachines/jdk1.8 .0_311 .jdk/Contents/Home/jre/lib/rt.jar file:/Library/Java/JavaVirtualMachines/jdk1.8 .0_311 .jdk/Contents/Home/jre/lib/sunrsasign.jar file:/Library/Java/JavaVirtualMachines/jdk1.8 .0_311 .jdk/Contents/Home/jre/lib/jsse.jar file:/Library/Java/JavaVirtualMachines/jdk1.8 .0_311 .jdk/Contents/Home/jre/lib/jce.jar file:/Library/Java/JavaVirtualMachines/jdk1.8 .0_311 .jdk/Contents/Home/jre/lib/charsets.jar file:/Library/Java/JavaVirtualMachines/jdk1.8 .0_311 .jdk/Contents/Home/jre/lib/jfr.jar file:/Library/Java/JavaVirtualMachines/jdk1.8 .0_311 .jdk/Contents/Home/jre/classes 扩展类加载器,加载以下文件: /Users/chensir/Library/Java/Extensions:/Library/Java/JavaVirtualMachines/jdk1.8 .0_311 .jdk/Contents/Home/jre/lib/ext:/Library/Java/Extensions:/Network/Library/Java/Extensions:/System/Library/Java/Extensions:/usr/lib/java =========================================== 应用类加载器,加载以下文件: /Library/Java/JavaVirtualMachines/jdk1.8 .0_311 .jdk/Contents/Home/jre/lib/charsets.jar:/Library/Java/JavaVirtualMachines/jdk1.8 .0_311 .jdk/Contents/Home/jre/lib/deploy.jar:/Library/Java/JavaVirtualMachines/jdk1.8 .0_311 .jdk/Contents/Home/jre/lib/ext/cldrdata.jar:/Library/Java/JavaVirtualMachines/jdk1.8 .0_311 .jdk/Contents/Home/jre/lib/ext/dnsns.jar:/Library/Java/JavaVirtualMachines/jdk1.8 .0_311 .jdk/Contents/Home/jre/lib/ext/jaccess.jar:/Library/Java/JavaVirtualMachines/jdk1.8 .0_311 .jdk/Contents/Home/jre/lib/ext/jfxrt.jar:/Library/Java/JavaVirtualMachines/jdk1.8 .0_311 .jdk/Contents/Home/jre/lib/ext/localedata.jar:/Library/Java/JavaVirtualMachines/jdk1.8 .0_311 .jdk/Contents/Home/jre/lib/ext/nashorn.jar:/Library/Java/JavaVirtualMachines/jdk1.8 .0_311 .jdk/Contents/Home/jre/lib/ext/sunec.jar:/Library/Java/JavaVirtualMachines/jdk1.8 .0_311 .jdk/Contents/Home/jre/lib/ext/sunjce_provider.jar:/Library/Java/JavaVirtualMachines/jdk1.8 .0_311 .jdk/Contents/Home/jre/lib/ext/sunpkcs11.jar:/Library/Java/JavaVirtualMachines/jdk1.8 .0_311 .jdk/Contents/Home/jre/lib/ext/zipfs.jar:/Library/Java/JavaVirtualMachines/jdk1.8 .0_311 .jdk/Contents/Home/jre/lib/javaws.jar:/Library/Java/JavaVirtualMachines/jdk1.8 .0_311 .jdk/Contents/Home/jre/lib/jce.jar:/Library/Java/JavaVirtualMachines/jdk1.8 .0_311 .jdk/Contents/Home/jre/lib/jfr.jar:/Library/Java/JavaVirtualMachines/jdk1.8 .0_311 .jdk/Contents/Home/jre/lib/jfxswt.jar:/Library/Java/JavaVirtualMachines/jdk1.8 .0_311 .jdk/Contents/Home/jre/lib/jsse.jar:/Library/Java/JavaVirtualMachines/jdk1.8 .0_311 .jdk/Contents/Home/jre/lib/management-agent.jar:/Library/Java/JavaVirtualMachines/jdk1.8 .0_311 .jdk/Contents/Home/jre/lib/plugin.jar:/Library/Java/JavaVirtualMachines/jdk1.8 .0_311 .jdk/Contents/Home/jre/lib/resources.jar:/Library/Java/JavaVirtualMachines/jdk1.8 .0_311 .jdk/Contents/Home/jre/lib/rt.jar:/Library/Java/JavaVirtualMachines/jdk1.8 .0_311 .jdk/Contents/Home/lib/ant-javafx.jar:/Library/Java/JavaVirtualMachines/jdk1.8 .0_311 .jdk/Contents/Home/lib/dt.jar:/Library/Java/JavaVirtualMachines/jdk1.8 .0_311 .jdk/Contents/Home/lib/javafx-mx.jar:/Library/Java/JavaVirtualMachines/jdk1.8 .0_311 .jdk/Contents/Home/lib/jconsole.jar:/Library/Java/JavaVirtualMachines/jdk1.8 .0_311 .jdk/Contents/Home/lib/packager.jar:/Library/Java/JavaVirtualMachines/jdk1.8 .0_311 .jdk/Contents/Home/lib/sa-jdi.jar:/Library/Java/JavaVirtualMachines/jdk1.8 .0_311 .jdk/Contents/Home/lib/tools.jar:/Users/chensir/Desktop/jvm/target/classes:/Users/chensir/Documents/MITR_Repository/Repository/org/springframework/boot/spring-boot-starter/2.7 .0 /spring-boot-starter-2.7 .0 .jar:/Users/chensir/Documents/MITR_Repository/Repository/org/springframework/boot/spring-boot/2.7 .0 /spring-boot-2.7 .0 .jar:/Users/chensir/Documents/MITR_Repository/Repository/org/springframework/spring-context/5.3 .20 /spring-context-5.3 .20 .jar:/Users/chensir/Documents/MITR_Repository/Repository/org/springframework/spring-aop/5.3 .20 /spring-aop-5.3 .20 .jar:/Users/chensir/Documents/MITR_Repository/Repository/org/springframework/spring-beans/5.3 .20 /spring-beans-5.3 .20 .jar:/Users/chensir/Documents/MITR_Repository/Repository/org/springframework/spring-expression/5.3 .20 /spring-expression-5.3 .20 .jar:/Users/chensir/Documents/MITR_Repository/Repository/org/springframework/boot/spring-boot-autoconfigure/2.7 .0 /spring-boot-autoconfigure-2.7 .0 .jar:/Users/chensir/Documents/MITR_Repository/Repository/org/springframework/boot/spring-boot-starter-logging/2.7 .0 /spring-boot-starter-logging-2.7 .0 .jar:/Users/chensir/Documents/MITR_Repository/Repository/ch/qos/logback/logback-classic/1.2 .11 /logback-classic-1.2 .11 .jar:/Users/chensir/Documents/MITR_Repository/Repository/ch/qos/logback/logback-core/1.2 .11 /logback-core-1.2 .11 .jar:/Users/chensir/Documents/MITR_Repository/Repository/org/apache/logging/log4j/log4j-to-slf4j/2.17 .2 /log4j-to-slf4j-2.17 .2 .jar:/Users/chensir/Documents/MITR_Repository/Repository/org/apache/logging/log4j/log4j-api/2.17 .2 /log4j-api-2.17 .2 .jar:/Users/chensir/Documents/MITR_Repository/Repository/org/slf4j/jul-to-slf4j/1.7 .36 /jul-to-slf4j-1.7 .36 .jar:/Users/chensir/Documents/MITR_Repository/Repository/jakarta/annotation/jakarta.annotation-api/1.3 .5 /jakarta.annotation-api-1.3 .5 .jar:/Users/chensir/Documents/MITR_Repository/Repository/org/springframework/spring-core/5.3 .20 /spring-core-5.3 .20 .jar:/Users/chensir/Documents/MITR_Repository/Repository/org/springframework/spring-jcl/5.3 .20 /spring-jcl-5.3 .20 .jar:/Users/chensir/Documents/MITR_Repository/Repository/org/yaml/snakeyaml/1.30 /snakeyaml-1.30 .jar:/Users/chensir/Documents/MITR_Repository/Repository/org/slf4j/slf4j-api/1.7 .36 /slf4j-api-1.7 .36 .jar:/Applications/IntelliJ IDEA.app/Contents/lib/idea_rt.jar
类加载器初始化过程 见上面的java类加载过程的大体流程图可知其中会创建JVM启动器实例 sun.misc.Launcher。
sun.misc.Launcher初始化使用了单例模式,保证一个JVM虚拟机内只有一个sun.misc.Launcher实例。
在Launcher构造方法内部,其创建了两个类加载器,分别是 sun.misc.Launcher.ExtClassLoader(扩展类加载器)和 sun.misc.Launcher.AppClassLoader(应用类加载器)。
jvm默认使用Launcher的getClassLoader()方法返回的类加载器AppCLassLoader的实例加载我们的应用程序。
public Launcher () { ExtClassLoader var1; try { var1 = Launcher.ExtClassLoader.getExtClassLoader(); } catch (IOException var10) { throw new InternalError("Could not create extension class loader" , var10); } try { this .loader = Launcher.AppClassLoader.getAppClassLoader(var1); } catch (IOException var9) { throw new InternalError("Could not create application class loader" , var9); } Thread.currentThread().setContextClassLoader(this .loader); String var2 = System.getProperty("java.security.manager" ); 。。。。。。 }
双亲委派机制 jvm类加载器是有亲子层级结构的,如下图
双亲委派机制: 加载某个类时会先委托父加载器寻找目标类,找不到在委托上层父加载器加载,如果所有父加载器在自己的加载类路径下都找不到目标类,则在自己的类加载路径中查找并载入目标类。
比如SayHello这个类,最先会找应用程序类加载器加载,应用程序类加载器会先委托扩展类加载器加载,扩展类加载器再委托引导类加载器,顶层引导类加载器在自己的类加载路径里没有找到SayHello类,则向下退回加载SayHello类的请求,扩展类加载器收到回复就自己加载,在自己的类加载路径里也没找到SayHello类,又向下退回SayHello类的加载请求给应用程序类加载器,应用程序类加载器于是在自己的类加载路径里找SayHello类,结果找到了就自己加载。双亲委派机制简单说就是,先找父加载器加载,找不到再由儿子自己加载。
AppCLassLoader的双亲委派机制 AppClassLoader的loadClass方法最终会调用其父类ClassLoader的loadClass方法,该方法大体逻辑如下:
1、先检查指定名称的类是否已经加载过,如果加载过了,就不需要加载,直接返回
2、如果此类没有加载过,再判断一下是否有父类加载器,如果有父类加载器,则由父类加载器加载(即调用parent.loadClass(name,false))或者是调用bootstrap类加载器来加载
3、如果父加载器及bootstrap类加载器都没有找到指定的类,那么调用当前类加载器的findClass方法来完成类加载
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; } }
为什么要设计双亲委派机制
沙箱安全机制:自己写的java.lang.String.class类不会被加载,这样可以防止核心API库被随意篡改
避免类的重复加载:当父亲已经加载了该类时,就没有必要让子类加载器ClassLoader再加载一次,保证被加载类的唯一性
类加载示例:
package java.lang;public class String { public static void main (String[] args) { System.out.println("My String Class" ); } } 运行结果: 错误: 在类 java.lang.String 中找不到 main 方法, 请将 main 方法定义为: public static void main (String[] args) 否则 JavaFX 应用程序类必须扩展javafx.application.Application
全盘负责委托机制 “全盘负责” 是指当一个ClassLoader装载一个类时,除非显示的使用另一个ClassLoader,该类所依赖及引用的类也由这个ClassLoader载入
自定义类加载器示例 自定义类加载器只需要继承java.lang.ClassLoader类
ClassLoader类有两个核心方法,一个是loadClass(String name, boolean resolve),实现了 双亲委派机制 ,还有一个方法是findClass(String name), 默认实现是空方法 ,所以我们自定义类加载器主要是重写 findClass方法
package ink.chensir.jvm;import java.io.FileInputStream;import java.lang.reflect.Method;public class MyClassLoaderTest { static class MyClassLoader extends ClassLoader { private String classPath; public MyClassLoader (String classPath) { this .classPath = classPath; } private byte [] loadByte(String name) throws Exception{ name = name.replaceAll("\\." , "/" ); FileInputStream fis = new FileInputStream(classPath + "/" + name + ".class" ); int len = fis.available(); byte [] data = new byte [len]; fis.read(data); fis.close(); return data; } @Override protected Class<?> findClass(String name) throws ClassNotFoundException { try { byte [] data = loadByte(name); return defineClass(name,data,0 ,data.length); } catch (Exception e) { throw new RuntimeException(e); } } } public static void main (String[] args) throws Exception { MyClassLoader classLoader = new MyClassLoader("/Users/chensir/Desktop/test" ); Class clazz = classLoader.loadClass("ink.chensir.jvm.SayHello1" ); Object obj = clazz.newInstance(); Method method = clazz.getDeclaredMethod("helloJvm" , null ); method.invoke(obj); System.out.println(clazz.getClassLoader().getClass().getName()); } } 运行结果: Hello Jvm1 ! =======自己的加载器加载类调用方法======= ink.chensir.jvm.MyClassLoaderTest$MyClassLoader
打破双亲委派机制 沙箱安全机制(自定义类加载器无法加载核心API)示例;
尝试打破双亲委派机制,用自定义的类加载器加载自己实现的 java.lang.String.class
package ink.chensir.jvm;import java.io.FileInputStream;import java.lang.reflect.Method;public class MyClassLoaderTest { static class MyClassLoader extends ClassLoader { private String classPath; public MyClassLoader (String classPath) { this .classPath = classPath; } private byte [] loadByte(String name) throws Exception{ name = name.replaceAll("\\." , "/" ); FileInputStream fis = new FileInputStream(classPath + "/" + name + ".class" ); int len = fis.available(); byte [] data = new byte [len]; fis.read(data); fis.close(); return data; } @Override protected Class<?> findClass(String name) throws ClassNotFoundException { try { byte [] data = loadByte(name); return defineClass(name,data,0 ,data.length); } catch (Exception e) { throw new RuntimeException(e); } } @Override protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { Class<?> c = findLoadedClass(name); if (c == null ) { long t0 = System.nanoTime(); if (!name.startsWith("ink.chensir.jvm" )) { c = this .getParent().loadClass(name); } else { c = findClass(name); } long t1 = System.nanoTime(); sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); sun.misc.PerfCounter.getFindClasses().increment(); } if (resolve) { resolveClass(c); } return c; } } } public static void main (String[] args) throws Exception { MyClassLoader classLoader = new MyClassLoader("/Users/chensir/Desktop/test" ); Class clazz = classLoader.loadClass("java.lang.String" ); Object obj = clazz.newInstance(); Method method = clazz.getDeclaredMethod("sayHello" , null ); method.invoke(obj); System.out.println(clazz.getClassLoader().getClass().getName()); } } 运行结果 Exception in thread "main" java.lang.RuntimeException: java.lang.SecurityException: Prohibited package name: java.lang at ink.chensir.jvm.MyClassLoaderTest$MyClassLoader.findClass(MyClassLoaderTest.java:37 ) at ink.chensir.jvm.MyClassLoaderTest$MyClassLoader.loadClass(MyClassLoaderTest.java:51 ) at java.lang.ClassLoader.loadClass(ClassLoader.java:351 ) at ink.chensir.jvm.MyClassLoaderTest.main(MyClassLoaderTest.java:70 ) Caused by: java.lang.SecurityException: Prohibited package name: java.lang at java.lang.ClassLoader.preDefineClass(ClassLoader.java:655 ) at java.lang.ClassLoader.defineClass(ClassLoader.java:754 ) at java.lang.ClassLoader.defineClass(ClassLoader.java:635 ) at ink.chensir.jvm.MyClassLoaderTest$MyClassLoader.findClass(MyClassLoaderTest.java:35 ) ... 3 more
Tomcat打破双亲委派机制 问题: Tomcat如果使用默认的双亲委派类加载机制行不行?
思考:Tomcat是web容器,那么它需要解决什么问题?
1、一个web容器可能需要部署两个应用程序,不同的应用程序可能会依赖同一个第三方类库的不同版本 ,不能要求同一个类库在同一个服务器只有一份,因此要保证每个应用程序的类库都是独立的,保证相互隔离。
2、部署在同一个web容器中相同的类库相同的版本可以共享 。否则,如果服务器有10个应用程序,那么要有10份相同的类库加载进虚拟机。
3、web容器也有自己的依赖库,不能与应用程序的类库混淆。 基于安全考虑,应该让容器的类库和程序的类库隔离开来。
4、web容器要支持jsp的修改,jsp文件最终也是要编译成class文件才能在虚拟机中运行,但程序运行后修改jsp已经是司空见惯的事情,web容器需要支持jsp修改后不用重启。
所以,Tomcat使用默认的双亲委派类加载机制是不行的
原因:1、如果使用默认类的加载机制,那么无法加载两个相同类库的不同版本,默认的类加载器是不管你是什么版本的,只在乎你的全限定类名,并且只有一份。
2、默认的类加载器是能够实现的,因为它的职责就是保证唯一性 。
3、与1一样
4、jsp文件其实也就是class文件,如果修改了,但类名还是一样,类加载器会直接去方法区中已经存在的,修改后的jsp是不会重新加载的。我们可以直接卸载掉这个jsp文件的类加载器,就是每个jsp文件对应一个唯一的类加载器,当一个jsp文件修改了,就直接卸载这个jsp类加载器。重新创建类加载器,重新加载jsp文件。
Tomcat类加载机制违背了java推荐的双亲委派模型,Tomcat为了实现隔离性,没有遵守约定,每个webAppClass加载自己的目录下的class文件,不会传递给父类加载器,打破了双亲委派机制
注意:同一个JVM,两个相同包名和类名的类对象可以共存,因为他们的类加载器可以不一样,所以看两个类对象是否是同一个,除了看类的包名和类名是否相同之外,还需要他们的类加载器也是同一个才能认为他们是同一个。
手写模拟简易Tomcat类加载 package ink.chensir.jvm;import java.io.FileInputStream;import java.lang.reflect.Method;public class MyClassLoaderTest { static class MyClassLoader extends ClassLoader { private String classPath; public MyClassLoader (String classPath) { this .classPath = classPath; } private byte [] loadByte(String name) throws Exception { name = name.replaceAll("\\." , "/" ); FileInputStream fis = new FileInputStream(classPath + "/" + name + ".class" ); int len = fis.available(); byte [] data = new byte [len]; fis.read(data); fis.close(); return data; } @Override protected Class<?> findClass(String name) throws ClassNotFoundException { try { byte [] data = loadByte(name); return defineClass(name, data, 0 , data.length); } catch (Exception e) { throw new RuntimeException(e); } } @Override protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { Class<?> c = findLoadedClass(name); if (c == null ) { long t0 = System.nanoTime(); if (!name.startsWith("ink.chensir.jvm" )) { c = this .getParent().loadClass(name); } else { c = findClass(name); } long t1 = System.nanoTime(); sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); sun.misc.PerfCounter.getFindClasses().increment(); } if (resolve) { resolveClass(c); } return c; } } } public static void main (String[] args) throws Exception { MyClassLoader classLoader1 = new MyClassLoader("/Users/chensir/Desktop/test1" ); Class clazz1 = classLoader1.loadClass("ink.chensir.jvm.SayHello1" ); Object obj1 = clazz1.newInstance(); Method method1 = clazz1.getDeclaredMethod("helloJvm" , null ); method1.invoke(obj1); System.out.println(clazz1.getClassLoader().getClass().getName()); MyClassLoader classLoader2 = new MyClassLoader("/Users/chensir/Desktop/test2" ); Class clazz2 = classLoader2.loadClass("ink.chensir.jvm.SayHello1" ); Object obj2 = clazz2.newInstance(); Method method2 = clazz2.getDeclaredMethod("helloJvm" , null ); method2.invoke(obj2); System.out.println(clazz2.getClassLoader().getClass().getName()); } }