概述
一、类加载运行的全过程
当我们用java命令运行某个类的main函数时,需要通过类加载器把主类加载到jvm中
public class User {
private String name;
private int age;
}
public class ClassLoaderTest1 {
public static final int count= 1;
public static User user = new User();
public int calculate() {
int x = 1;
int y = 2;
int z = x * y;
return z;
}
public static void main(String[] args) {
ClassLoaderTest1 loaderTest1 = new ClassLoaderTest1();
loaderTest1.calculate();
}
}
通过java命令执行代码的流程大致如下:
其中,加载类的过程如下:
加载 >> 验证 >> 准备 >> 解析 >> 初始化 >> 使用 >> 卸载
加载:在硬盘上查找并通过IO读入字节码文件,使用到类时才会加载,例如调用类的 main()方法,new对象等等,在加载阶段会在内存中生成一个代表这个类的 java.lang.Class对象,作为方法区这个类的各种数据的访问入口
验证:校验字节码文件的正确性,比如开头:cafe babe,是否java语言的规范
准备:给类的静态变量分配内存,并赋予默认值
public static final int count= 1; //静态变量赋一个默认值 int->0
public static User user = new User(); //对象->null
解析:将符号引用替换为直接引用,该阶段会把一些静态方法(符号引用,比如 main()方法)替换为指向数据所存内存的指针或句柄等(直接引用),这是所谓的静态链接过程(类加载期间完成),动态链接是在程序运行期间完成的将符号引用替换为直接引用javap -v ClassLoaderTest1.class
Classfile /E:/code/gitee/springboot-examples/springboot-examples-demo/jvm-demo/target/test-classes/com/zengqingfa/exercise/jvm/ClassLoaderTest1.class
Last modified 2021-10-23; size 889 bytes
MD5 checksum e05503781b6953f2b3a4729adee10dba
Compiled from "ClassLoaderTest1.java"
public class com.zengqingfa.exercise.jvm.ClassLoaderTest1
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #8.#36 // java/lang/Object."<init>":()V
#2 = Class #37 // com/zengqingfa/exercise/jvm/ClassLoaderTest1
#3 = Methodref #2.#36 // com/zengqingfa/exercise/jvm/ClassLoaderTest1."<init>":()V
#4 = Methodref #2.#38 // com/zengqingfa/exercise/jvm/ClassLoaderTest1.calculate:()I
#5 = Class #39 // com/zengqingfa/exercise/jvm/User
#6 = Methodref #5.#36 // com/zengqingfa/exercise/jvm/User."<init>":()V
#7 = Fieldref #2.#40 // com/zengqingfa/exercise/jvm/ClassLoaderTest1.user:Lcom/zengqingfa/exercise/jvm/User;
#8 = Class #41 // java/lang/Object
#9 = Utf8 count
#10 = Utf8 I
#11 = Utf8 ConstantValue
#12 = Integer 1
#13 = Utf8 user
#14 = Utf8 Lcom/zengqingfa/exercise/jvm/User;
#15 = Utf8 <init>
#16 = Utf8 ()V
#17 = Utf8 Code
#18 = Utf8 LineNumberTable
#19 = Utf8 LocalVariableTable
#20 = Utf8 this
#21 = Utf8 Lcom/zengqingfa/exercise/jvm/ClassLoaderTest1;
#22 = Utf8 calculate
#23 = Utf8 ()I
#24 = Utf8 x
#25 = Utf8 y
#26 = Utf8 z
#27 = Utf8 main
#28 = Utf8 ([Ljava/lang/String;)V
#29 = Utf8 args
#30 = Utf8 [Ljava/lang/String;
#31 = Utf8 loaderTest1
#32 = Utf8 MethodParameters
#33 = Utf8 <clinit>
#34 = Utf8 SourceFile
#35 = Utf8 ClassLoaderTest1.java
#36 = NameAndType #15:#16 // "<init>":()V
#37 = Utf8 com/zengqingfa/exercise/jvm/ClassLoaderTest1
#38 = NameAndType #22:#23 // calculate:()I
#39 = Utf8 com/zengqingfa/exercise/jvm/User
#40 = NameAndType #13:#14 // user:Lcom/zengqingfa/exercise/jvm/User;
#41 = Utf8 java/lang/Object
{
public static final int count;
descriptor: I
flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
ConstantValue: int 1
public static com.zengqingfa.exercise.jvm.User user;
descriptor: Lcom/zengqingfa/exercise/jvm/User;
flags: ACC_PUBLIC, ACC_STATIC
public com.zengqingfa.exercise.jvm.ClassLoaderTest1();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 9: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/zengqingfa/exercise/jvm/ClassLoaderTest1;
public int calculate();
descriptor: ()I
flags: ACC_PUBLIC
Code:
stack=2, locals=4, args_size=1
0: iconst_1
1: istore_1
2: iconst_2
3: istore_2
4: iload_1
5: iload_2
6: imul
7: istore_3
8: iload_3
9: ireturn
LineNumberTable:
line 15: 0
line 16: 2
line 17: 4
line 18: 8
LocalVariableTable:
Start Length Slot Name Signature
0 10 0 this Lcom/zengqingfa/exercise/jvm/ClassLoaderTest1;
2 8 1 x I
4 6 2 y I
8 2 3 z I
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=2, args_size=1
0: new #2 // class com/zengqingfa/exercise/jvm/ClassLoaderTest1
3: dup
4: invokespecial #3 // Method "<init>":()V
7: astore_1
8: aload_1
9: invokevirtual #4 // Method calculate:()I
12: pop
13: return
LineNumberTable:
line 22: 0
line 23: 8
line 24: 13
LocalVariableTable:
Start Length Slot Name Signature
0 14 0 args [Ljava/lang/String;
8 6 1 loaderTest1 Lcom/zengqingfa/exercise/jvm/ClassLoaderTest1;
MethodParameters:
Name Flags
args
static {};
descriptor: ()V
flags: ACC_STATIC
Code:
stack=2, locals=0, args_size=0
0: new #5 // class com/zengqingfa/exercise/jvm/User
3: dup
4: invokespecial #6 // Method com/zengqingfa/exercise/jvm/User."<init>":()V
7: putstatic #7 // Field user:Lcom/zengqingfa/exercise/jvm/User;
10: return
LineNumberTable:
line 12: 0
}
SourceFile: "ClassLoaderTest1.java"
初始化:对类的静态变量初始化为指定的值,执行静态代码块
public static final int count= 1; //赋值1
public static User user = new User(); //赋值对象user
类被加载到方法区中后主要包含运行时常量池、类型信息、字段信息、方法信息、类加载器的 引用、对应class实例的引用等信息。
类加载器的引用:这个类到类加载器实例的引用 对应class实例的引用:类加载器在加载类信息放到方法区中后,会创建一个对应的Class 类型的 对象实例放到堆(Heap)中, 作为开发人员访问方法区中类定义的入口和切入点。
**注意,主类在运行过程中如果使用到其它类,会逐步加载这些类,为懒加载。 **
jar包或war包里的类不是一次性全部加载的,是使用到时才加载。
public class LazyLoadTest {
static {
System.out.println("LazyLoadTest 静态代码块");
}
public static void main(String[] args) {
new A();
System.out.println("============== load test==============");
B b=null;//这里不会加载B,除非new B()
}
}
class A {
static {
System.out.println("==============静态代码块加载 AAAAA==============");
}
public A() {
System.out.println("==============构造函数加载 AAAAA==============");
}
}
class B {
static {
System.out.println("==============静态代码块加载 BBBBB==============");
}
public B() {
System.out.println("==============构造函数加载 BBBBB==============");
}
}
执行结果:
LazyLoadTest 静态代码块
==============静态代码块加载 AAAAA==============
==============构造函数加载 AAAAA==============
============== load test==============
二、类加载器和双亲委派机制
2.1 类加载器
上面的类加载过程主要是通过类加载器来实现的,Java里有如下几种类加载器
引导类加载器:负责加载支撑JVM运行的位于JRE的lib目录下的核心类库,比如 rt.jar、charsets.jar等
扩展类加载器:负责加载支撑JVM运行的位于JRE的lib目录下的ext扩展目录中的JAR 类包
应用程序类加载器:负责加载ClassPath路径下的类包,主要就是加载你自己写的那些类
自定义加载器:负责加载用户自定义路径下的类包
测试类加载器
package com.zengqingfa.exercise.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 bootstrapLoader = extClassloader.getParent();
System.out.println("the bootstrapLoader : " + bootstrapLoader);
System.out.println("the extClassloader : " + extClassloader);
System.out.println("the appClassLoader : " + appClassLoader);
System.out.println();
System.out.println("bootstrapLoader加载以下文件:");
URL[] urls = Launcher.getBootstrapClassPath().getURLs();
for (int i = 0; i < urls.length; i++) {
System.out.println(urls[i]);
}
System.out.println();
System.out.println("extClassloader加载以下文件:");
System.out.println(System.getProperty("java.ext.dirs"));
System.out.println();
System.out.println("appClassLoader加载以下文件:");
System.out.println(System.getProperty("java.class.path"));
}
}
测试结果
null
sun.misc.Launcher$ExtClassLoader
sun.misc.Launcher$AppClassLoader
=======================
the bootstrapLoader : null
the extClassloader : sun.misc.Launcher$ExtClassLoader@1b2c6ec2
the appClassLoader : sun.misc.Launcher$AppClassLoader@18b4aac2
bootstrapLoader加载以下文件:
file:/F:/jdk1.8/jre/lib/resources.jar
file:/F:/jdk1.8/jre/lib/rt.jar
file:/F:/jdk1.8/jre/lib/sunrsasign.jar
file:/F:/jdk1.8/jre/lib/jsse.jar
file:/F:/jdk1.8/jre/lib/jce.jar
file:/F:/jdk1.8/jre/lib/charsets.jar
file:/F:/jdk1.8/jre/lib/jfr.jar
file:/F:/jdk1.8/jre/classes
extClassloader加载以下文件:
F:jdk1.8jrelibext;C:WINDOWSSunJavalibext
appClassLoader加载以下文件:
F:jdk1.8jrelibcharsets.jar;F:jdk1.8jrelibdeploy.jar;F:jdk1.8jrelibextaccess-bridge-64.jar;F:jdk1.8jrelibextcldrdata.jar;F:jdk1.8jrelibextdnsns.jar;F:jdk1.8jrelibextjaccess.jar;F:jdk1.8jrelibextjfxrt.jar;F:jdk1.8jrelibextlocaledata.jar;F:jdk1.8jrelibextnashorn.jar;F:jdk1.8jrelibextsunec.jar;F:jdk1.8jrelibextsunjce_provider.jar;F:jdk1.8jrelibextsunmscapi.jar;F:jdk1.8jrelibextsunpkcs11.jar;F:jdk1.8jrelibextzipfs.jar;F:jdk1.8jrelibjavaws.jar;F:jdk1.8jrelibjce.jar;F:jdk1.8jrelibjfr.jar;F:jdk1.8jrelibjfxswt.jar;F:jdk1.8jrelibjsse.jar;F:jdk1.8jrelibmanagement-agent.jar;F:jdk1.8jrelibplugin.jar;F:jdk1.8jrelibresources.jar;F:jdk1.8jrelibrt.jar;E:codegiteespringboot-examplesspringboot-examples-demojvm-demotargettest-classes;E:apache-maven-3.6.0repositoryorgspringframeworkbootspring-boot-starter-web2.2.0.RELEASEspring-boot-starter-web-2.2.0.RELEASE.jar;E:apache-maven-3.6.0repositoryorgspringframeworkbootspring-boot-starter2.2.0.RELEASEspring-boot-starter-2.2.0.RELEASE.jar;E:apache-maven-3.6.0repositoryorgspringframeworkbootspring-boot2.2.0.RELEASEspring-boot-2.2.0.RELEASE.jar;E:apache-maven-3.6.0repositoryorgspringframeworkbootspring-boot-autoconfigure2.2.0.RELEASEspring-boot-autoconfigure-2.2.0.RELEASE.jar;E:apache-maven-3.6.0repositoryorgspringframeworkbootspring-boot-starter-logging2.2.0.RELEASEspring-boot-starter-logging-2.2.0.RELEASE.jar;E:apache-maven-3.6.0repositorychqoslogbacklogback-classic1.2.3logback-classic-1.2.3.jar;E:apache-maven-3.6.0repositorychqoslogbacklogback-core1.2.3logback-core-1.2.3.jar;E:apache-maven-3.6.0repositoryorgapachelogginglog4jlog4j-to-slf4j2.12.1log4j-to-slf4j-2.12.1.jar;E:apache-maven-3.6.0repositoryorgapachelogginglog4jlog4j-api2.12.1log4j-api-2.12.1.jar;E:apache-maven-3.6.0repositoryorgslf4jjul-to-slf4j1.7.28jul-to-slf4j-1.7.28.jar;E:apache-maven-3.6.0repositoryjakartaannotationjakarta.annotation-api1.3.5jakarta.annotation-api-1.3.5.jar;E:apache-maven-3.6.0repositoryorgyamlsnakeyaml1.25snakeyaml-1.25.jar;E:apache-maven-3.6.0repositoryorgspringframeworkbootspring-boot-starter-json2.2.0.RELEASEspring-boot-starter-json-2.2.0.RELEASE.jar;E:apache-maven-3.6.0repositorycomfasterxmljacksoncorejackson-databind2.10.0jackson-databind-2.10.0.jar;E:apache-maven-3.6.0repositorycomfasterxmljacksoncorejackson-annotations2.10.0jackson-annotations-2.10.0.jar;E:apache-maven-3.6.0repositorycomfasterxmljacksoncorejackson-core2.10.0jackson-core-2.10.0.jar;E:apache-maven-3.6.0repositorycomfasterxmljacksondatatypejackson-datatype-jdk82.10.0jackson-datatype-jdk8-2.10.0.jar;E:apache-maven-3.6.0repositorycomfasterxmljacksondatatypejackson-datatype-jsr3102.10.0jackson-datatype-jsr310-2.10.0.jar;E:apache-maven-3.6.0repositorycomfasterxmljacksonmodulejackson-module-parameter-names2.10.0jackson-module-parameter-names-2.10.0.jar;E:apache-maven-3.6.0repositoryorgspringframeworkbootspring-boot-starter-tomcat2.2.0.RELEASEspring-boot-starter-tomcat-2.2.0.RELEASE.jar;E:apache-maven-3.6.0repositoryorgapachetomcatembedtomcat-embed-core9.0.27tomcat-embed-core-9.0.27.jar;E:apache-maven-3.6.0repositoryorgapachetomcatembedtomcat-embed-el9.0.27tomcat-embed-el-9.0.27.jar;E:apache-maven-3.6.0repositoryorgapachetomcatembedtomcat-embed-websocket9.0.27tomcat-embed-websocket-9.0.27.jar;E:apache-maven-3.6.0repositoryorgspringframeworkbootspring-boot-starter-validation2.2.0.RELEASEspring-boot-starter-validation-2.2.0.RELEASE.jar;E:apache-maven-3.6.0repositoryjakartavalidationjakarta.validation-api2.0.1jakarta.validation-api-2.0.1.jar;E:apache-maven-3.6.0repositoryorghibernatevalidatorhibernate-validator6.0.17.Finalhibernate-validator-6.0.17.Final.jar;E:apache-maven-3.6.0repositoryorgjbossloggingjboss-logging3.4.1.Finaljboss-logging-3.4.1.Final.jar;E:apache-maven-3.6.0repositorycomfasterxmlclassmate1.5.0classmate-1.5.0.jar;E:apache-maven-3.6.0repositoryorgspringframeworkspring-web5.2.0.RELEASEspring-web-5.2.0.RELEASE.jar;E:apache-maven-3.6.0repositoryorgspringframeworkspring-beans5.2.0.RELEASEspring-beans-5.2.0.RELEASE.jar;E:apache-maven-3.6.0repositoryorgspringframeworkspring-webmvc5.2.0.RELEASEspring-webmvc-5.2.0.RELEASE.jar;E:apache-maven-3.6.0repositoryorgspringframeworkspring-aop5.2.0.RELEASEspring-aop-5.2.0.RELEASE.jar;E:apache-maven-3.6.0repositoryorgspringframeworkspring-context5.2.0.RELEASEspring-context-5.2.0.RELEASE.jar;E:apache-maven-3.6.0repositoryorgspringframeworkspring-expression5.2.0.RELEASEspring-expression-5.2.0.RELEASE.jar;E:apache-maven-3.6.0repositoryorgprojectlomboklombok1.18.12lombok-1.18.12.jar;E:apache-maven-3.6.0repositorycomgoogleguavaguava29.0-jreguava-29.0-jre.jar;E:apache-maven-3.6.0repositorycomgoogleguavafailureaccess1.0.1failureaccess-1.0.1.jar;E:apache-maven-3.6.0repositorycomgoogleguavalistenablefuture9999.0-empty-to-avoid-conflict-with-guavalistenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar;E:apache-maven-3.6.0repositorycomgooglecodefindbugsjsr3053.0.2jsr305-3.0.2.jar;E:apache-maven-3.6.0repositoryorgcheckerframeworkchecker-qual2.11.1checker-qual-2.11.1.jar;E:apache-maven-3.6.0repositorycomgoogleerrorproneerror_prone_annotations2.3.4error_prone_annotations-2.3.4.jar;E:apache-maven-3.6.0repositorycomgooglej2objcj2objc-annotations1.3j2objc-annotations-1.3.jar;E:apache-maven-3.6.0repositoryorgspringframeworkbootspring-boot-starter-test2.2.0.RELEASEspring-boot-starter-test-2.2.0.RELEASE.jar;E:apache-maven-3.6.0repositoryorgspringframeworkbootspring-boot-test2.2.0.RELEASEspring-boot-test-2.2.0.RELEASE.jar;E:apache-maven-3.6.0repositoryorgspringframeworkbootspring-boot-test-autoconfigure2.2.0.RELEASEspring-boot-test-autoconfigure-2.2.0.RELEASE.jar;E:apache-maven-3.6.0repositorycomjaywayjsonpathjson-path2.4.0json-path-2.4.0.jar;E:apache-maven-3.6.0repositorynetminidevjson-smart2.3json-smart-2.3.jar;E:apache-maven-3.6.0repositorynetminidevaccessors-smart1.2accessors-smart-1.2.jar;E:apache-maven-3.6.0repositoryorgow2asmasm5.0.4asm-5.0.4.jar;E:apache-maven-3.6.0repositoryorgslf4jslf4j-api1.7.28slf4j-api-1.7.28.jar;E:apache-maven-3.6.0repositoryjakartaxmlbindjakarta.xml.bind-api2.3.2jakarta.xml.bind-api-2.3.2.jar;E:apache-maven-3.6.0repositoryjakartaactivationjakarta.activation-api1.2.1jakarta.activation-api-1.2.1.jar;E:apache-maven-3.6.0repositoryorgjunitjupiterjunit-jupiter5.5.2junit-jupiter-5.5.2.jar;E:apache-maven-3.6.0repositoryorgjunitjupiterjunit-jupiter-api5.5.2junit-jupiter-api-5.5.2.jar;E:apache-maven-3.6.0repositoryorgopentest4jopentest4j1.2.0opentest4j-1.2.0.jar;E:apache-maven-3.6.0repositoryorgjunitplatformjunit-platform-commons1.5.2junit-platform-commons-1.5.2.jar;E:apache-maven-3.6.0repositoryorgjunitjupiterjunit-jupiter-params5.5.2junit-jupiter-params-5.5.2.jar;E:apache-maven-3.6.0repositoryorgjunitjupiterjunit-jupiter-engine5.5.2junit-jupiter-engine-5.5.2.jar;E:apache-maven-3.6.0repositoryorgjunitvintagejunit-vintage-engine5.5.2junit-vintage-engine-5.5.2.jar;E:apache-maven-3.6.0repositoryorgapiguardianapiguardian-api1.1.0apiguardian-api-1.1.0.jar;E:apache-maven-3.6.0repositoryorgjunitplatformjunit-platform-engine1.5.2junit-platform-engine-1.5.2.jar;E:apache-maven-3.6.0repositoryjunitjunit4.12junit-4.12.jar;E:apache-maven-3.6.0repositoryorgmockitomockito-junit-jupiter3.1.0mockito-junit-jupiter-3.1.0.jar;E:apache-maven-3.6.0repositoryorgassertjassertj-core3.13.2assertj-core-3.13.2.jar;E:apache-maven-3.6.0repositoryorghamcresthamcrest2.1hamcrest-2.1.jar;E:apache-maven-3.6.0repositoryorgmockitomockito-core3.1.0mockito-core-3.1.0.jar;E:apache-maven-3.6.0repositorynetbytebuddybyte-buddy1.10.1byte-buddy-1.10.1.jar;E:apache-maven-3.6.0repositorynetbytebuddybyte-buddy-agent1.10.1byte-buddy-agent-1.10.1.jar;E:apache-maven-3.6.0repositoryorgobjenesisobjenesis2.6objenesis-2.6.jar;E:apache-maven-3.6.0repositoryorgskyscreamerjsonassert1.5.0jsonassert-1.5.0.jar;E:apache-maven-3.6.0repositorycomvaadinexternalgoogleandroid-json0.0.20131108.vaadin1android-json-0.0.20131108.vaadin1.jar;E:apache-maven-3.6.0repositoryorgspringframeworkspring-core5.2.0.RELEASEspring-core-5.2.0.RELEASE.jar;E:apache-maven-3.6.0repositoryorgspringframeworkspring-jcl5.2.0.RELEASEspring-jcl-5.2.0.RELEASE.jar;E:apache-maven-3.6.0repositoryorgspringframeworkspring-test5.2.0.RELEASEspring-test-5.2.0.RELEASE.jar;E:apache-maven-3.6.0repositoryorgxmlunitxmlunit-core2.6.3xmlunit-core-2.6.3.jar;E:apache-maven-3.6.0repositorycomalibabafastjson1.2.70fastjson-1.2.70.jar;E:apache-maven-3.6.0repositorycomgooglecodegsongson2.8.6gson-2.8.6.jar;E:apache-maven-3.6.0repositorycnhutoolhutool-all5.3.7hutool-all-5.3.7.jar;E:apache-maven-3.6.0repositoryorgapachecommonscommons-lang33.9commons-lang3-3.9.jar;F:IDEAideaIU-2020.3.2.winlibidea_rt.jar
可以看到,appClassLoader加载的类的目录也包含bootstrapLoader加载的目录。
类加载器初始化过程
由引导类加载器通过c++代码发起sun.misc.Launcher#getLauncher方法的调用,会创建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 class Launcher {
private static URLStreamHandlerFactory factory = new Launcher.Factory();
//静态成员变量 只有一个实例
private static Launcher launcher = new Launcher();
private static String bootClassPath = System.getProperty("sun.boot.class.path");
private ClassLoader loader;
private static URLStreamHandler fileHandler;
public static Launcher getLauncher() {
return launcher;
}
public Launcher() {
Launcher.ExtClassLoader var1;
try {
//1、获取扩展类加载器实例,并把父类加载器设置为空
var1 = Launcher.ExtClassLoader.getExtClassLoader();
} catch (IOException var10) {
throw new InternalError("Could not create extension class loader", var10);
}
try {
//2、获取应用类加载器实例,把扩展类加载器作为参数传入
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");
...
}
public ClassLoader getClassLoader() {
return this.loader;
}
static class AppClassLoader extends URLClassLoader {
final URLClassPath ucp = SharedSecrets.getJavaNetAccess().getURLClassPath(this);
//2.1 获取应用程序类扩展加载器
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<Launcher.AppClassLoader>() {
public Launcher.AppClassLoader run() {
URL[] var1x = var1 == null ? new URL[0] : Launcher.pathToURLs(var2);
//2.2 调用应用程序类扩展加载器构造方法 var0为扩展类加载器
return new Launcher.AppClassLoader(var1x, var0);
}
});
}
// 2.3 应用程序类扩展加载器构造方法,var2为扩展类加载器
AppClassLoader(URL[] var1, ClassLoader var2) {
//2.4 调用父类的构造函数,var2为扩展类加载器作为参数传入
//最后调用java.lang.ClassLoader#ClassLoader(java.lang.Void, java.lang.ClassLoader)
//有个成员变量parent 进行赋值操作
super(var1, var2, Launcher.factory);
this.ucp.initLookupCache(this);
}
}
static class ExtClassLoader extends URLClassLoader {
private static volatile Launcher.ExtClassLoader instance;
//1.1 获取扩展类加载器
public static Launcher.ExtClassLoader getExtClassLoader() throws IOException {
if (instance == null) {
Class var0 = Launcher.ExtClassLoader.class;
synchronized(Launcher.ExtClassLoader.class) {
if (instance == null) {
//1.2 创建扩展类加载器方法调用
instance = createExtClassLoader();
}
}
}
return instance;
}
//1.3 创建扩展类加载器方法
private static Launcher.ExtClassLoader createExtClassLoader() throws IOException {
try {
return (Launcher.ExtClassLoader)AccessController.doPrivileged(new PrivilegedExceptionAction<Launcher.ExtClassLoader>() {
public Launcher.ExtClassLoader run() throws IOException {
File[] var1 = Launcher.ExtClassLoader.getExtDirs();
int var2 = var1.length;
for(int var3 = 0; var3 < var2; ++var3) {
MetaIndex.registerDirectory(var1[var3]);
}
//1.4 创建扩展类加载器,调用构造方法
return new Launcher.ExtClassLoader(var1);
}
});
} catch (PrivilegedActionException var1) {
throw (IOException)var1.getException();
}
}
//1.5 传入文件路径进行加载
public ExtClassLoader(File[] var1) throws IOException {
//1.6 调用父类的构造方法 ,第二个参数为parent,传入的为空
//java.net.URLClassLoader#URLClassLoader(java.net.URL[], java.lang.ClassLoader, java.net.URLStreamHandlerFactory)
super(getExtURLs(var1), (ClassLoader)null, Launcher.factory);
SharedSecrets.getJavaNetAccess().getURLClassPath(this).initLookupCache(this);
}
}
//参考类
//java.net.URLClassLoader#URLClassLoader
public URLClassLoader(URL[] urls, ClassLoader parent,
URLStreamHandlerFactory factory) {
super(parent);
// this is to make the stack depth consistent with 1.1
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkCreateClassLoader();
}
acc = AccessController.getContext();
ucp = new URLClassPath(urls, factory, acc);
}
//java.security.SecureClassLoader#SecureClassLoader(java.lang.ClassLoader)
protected SecureClassLoader(ClassLoader parent) {
super(parent);
// this is to make the stack depth consistent with 1.1
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkCreateClassLoader();
}
initialized = true;
}
//java.lang.ClassLoader#ClassLoader(java.lang.ClassLoader)
public abstract class ClassLoader {
private static native void registerNatives();
static {
registerNatives();
}
// The parent class loader for delegation
// Note: VM hardcoded the offset of this field, thus all new fields
// must be added *after* it.
private final ClassLoader parent;
protected ClassLoader(ClassLoader parent) {
this(checkCreateClassLoader(), parent);
}
private ClassLoader(Void unused, ClassLoader parent) {
this.parent = parent;
if (ParallelLoaders.isRegistered(this.getClass())) {
parallelLockMap = new ConcurrentHashMap<>();
package2certs = new ConcurrentHashMap<>();
domains =
Collections.synchronizedSet(new HashSet<ProtectionDomain>());
assertionLock = new Object();
} else {
// no finer-grained lock; lock on the classloader instance
parallelLockMap = null;
package2certs = new Hashtable<>();
domains = new HashSet<>();
assertionLock = this;
}
}
...
}
类加载器结构图
2.2 双亲委派机制
JVM类加载器是有亲子层级结构的
这里类加载其实就有一个双亲委派机制,加载某个类时会先委托父加载器寻找目标类,找不到再 委托上层父加载器加载,如果所有父加载器在自己的加载类路径下都找不到目标类,则在自己的 类加载路径中查找并载入目标类。
比如我们的ClassLoaderTest类,最先会找应用程序类加载器加载,应用程序类加载器会先委托扩展类加载 器加载,扩展类加载器再委托引导类加载器,顶层引导类加载器在自己的类加载路径里找了半天 没找到ClassLoaderTest类,则向下退回加载ClassLoaderTest类的请求,扩展类加载器收到回复就自己加载,在自己的 类加载路径里找了半天也没找到ClassLoaderTest类,又向下退回ClassLoaderTest类的加载请求给应用程序类加载器, 应用程序类加载器于是在自己的类加载路径里找Math类,结果找到了就自己加载了。。
双亲委派机制说简单点就是,先找父亲加载,不行再由儿子自己加载
源码解析:
为什么开始是从应用程序类加载器开始进行加载呢?AppClassLoader
不是从引导类加载器开始加载呢?一次循环就可以了,90%的应用程序的代码都是由应用程序类加载的
只是初始化类的时候需要2次循环,加载完毕后,会保存下来java.lang.ClassLoader#findLoadedClass
,下次使用的时候直接从应用程序类判断是否加载过,如果加载过,直接返回。
public class Launcher {
...
private ClassLoader loader;
public ClassLoader getClassLoader() {
return this.loader;
}
public Launcher() {
Launcher.ExtClassLoader var1;
try {
var1 = Launcher.ExtClassLoader.getExtClassLoader();
} catch (IOException var10) {
throw new InternalError("Could not create extension class loader", var10);
}
try {
//load 赋值为应用程序类加载器
this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
} catch (IOException var9) {
throw new InternalError("Could not create application class loader", var9);
}
}
...
}
通过sun.misc.Launcher类的成员变量loader进行类的加载
通过调用其父类java.lang.ClassLoader#loadClass(java.lang.String) 方法加载类
方法逻辑如下:
1. 首先,检查一下指定名称的类是否已经加载过,如果加载过了,就不需要再加载,直接 返回。
2. 如果此类没有加载过,那么,再判断一下是否有父加载器;如果有父加载器,则由父加 载器加载(即调用parent.loadClass(name, false);).或者是调用bootstrap类加载器来加 载。
3. 如果父加载器及bootstrap类加载器都没有找到指定的类,那么调用当前类加载器的 findClass方法来完成类加载。
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
//检查该类是否已经加载过,如果加载过,直接返回,没有加载过,返回空
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
//appClassLoader的parent属性为extClassLoader
if (parent != null) {
c = parent.loadClass(name, false);
} else {
//由引导类加载器进行加载
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
//如果加载失败,则尝试自己去加载
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
//都会调用URLClassLoader的findClass方法在加载器的类路径里查找并加载该类
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
//通过全限定类名获取已经加载过的类
protected final Class<?> findLoadedClass(String name) {
if (!checkName(name))
return null;
return findLoadedClass0(name);
}
private native final Class<?> findLoadedClass0(String name);
protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
调用父类的java.net.URLClassLoader#findClass
方法进行类的加载
protected Class<?> findClass(final String name)
throws ClassNotFoundException
{
final Class<?> result;
try {
result = AccessController.doPrivileged(
new PrivilegedExceptionAction<Class<?>>() {
public Class<?> run() throws ClassNotFoundException {
//com/zengqingfa/exercise/jvm/ClassLoaderTest1.class
String path = name.replace('.', '/').concat(".class");
//当前类的加载的路径进行加载,如果没有则返回空
Resource res = ucp.getResource(path, false);
if (res != null) {
try {
//类加载过程
return defineClass(name, res);
} catch (IOException e) {
throw new ClassNotFoundException(name, e);
}
} else {
return null;
}
}
}, acc);
} catch (java.security.PrivilegedActionException pae) {
throw (ClassNotFoundException) pae.getException();
}
if (result == null) {
throw new ClassNotFoundException(name);
}
return result;
}
为什么要设计双亲委派机制
**沙箱安全机制:自己写的java.lang.String.class类不会被加载,这样便可以防止核心 API库被随意篡改 **
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
类加载过程中是返回rt.jar中jdk中的String类,是不存在的main方法
避免类的重复加载:当父亲已经加载了该类时,就没有必要子ClassLoader再加载一 次,保证被加载类的唯一性
全盘负责委托机制
“全盘负责”是指当一个ClassLoder装载一个类时,除非显示的使用另外一个ClassLoder,该类所依赖及引用的类也由这个ClassLoder载入。User类和ClassLoaderTest1类使用同一个类加载器进行加载
public class ClassLoaderTest1 {
public static User user = new User();
}
自定义类加载器示例
自定义类加载器只需要继承 java.lang.ClassLoader 类,该类有两个核心方法,一个是 loadClass(String, boolean),实现了双亲委派机制,还有一个方法是findClass,默认实现是空 方法,所以我们自定义类加载器主要是重写findClass方法。
需要加载的类:
public class User1 {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public void print() {
System.out.println("自定义类加载器加载");
}
}
自定义类加载器
package com.zengqingfa.exercise.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;
}
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
byte[] data = loadByte(name);
//defineClass将一个字节数组转为Class对象,这个字节数组是class文件读取后最终的字节 数组。
return defineClass(name, data, 0, data.length);
} catch (Exception e) {
e.printStackTrace();
throw new ClassNotFoundException();
}
}
}
public static void main(String args[]) throws Exception {
//初始化自定义类加载器,会先初始化父类ClassLoader,其中会把自定义类加载器的父加载
//器设置为应用程序类加载器AppClassLoader
MyClassLoader classLoader = new MyClassLoader("F:/test");
//F盘创建 F:testcomzengqingfaexercisejvm 几级目录,将User类的复制类User1.class丢入该目录
Class clazz = classLoader.loadClass("com.zengqingfa.exercise.jvm.User1");
Object obj = clazz.newInstance();
Method method = clazz.getDeclaredMethod("print", null);
method.invoke(obj, null);
//如果父类appClassLoader存在该类 sun.misc.Launcher$AppClassLoader
//如果父类appClassLoader不存在该类com.zengqingfa.exercise.jvm.MyClassLoaderTest$MyClassLoader
System.out.println(clazz.getClassLoader().getClass().getName());
}
}
自定义类载器的父类加载器为什么是应用程序类加载器?
初始化自定义类加载器的时候,先初始化父类加载器
protected ClassLoader() {
this(checkCreateClassLoader(), getSystemClassLoader());
}
protected ClassLoader(ClassLoader parent) {
this(checkCreateClassLoader(), parent);
}
private ClassLoader(Void unused, ClassLoader parent) {
this.parent = parent;
if (ParallelLoaders.isRegistered(this.getClass())) {
parallelLockMap = new ConcurrentHashMap<>();
package2certs = new ConcurrentHashMap<>();
domains =
Collections.synchronizedSet(new HashSet<ProtectionDomain>());
assertionLock = new Object();
} else {
// no finer-grained lock; lock on the classloader instance
parallelLockMap = null;
package2certs = new Hashtable<>();
domains = new HashSet<>();
assertionLock = this;
}
}
public static ClassLoader getSystemClassLoader() {
initSystemClassLoader();
if (scl == null) {
return null;
}
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkClassLoaderPermission(scl, Reflection.getCallerClass());
}
return scl;
}
private static synchronized void initSystemClassLoader() {
if (!sclSet) {
if (scl != null)
throw new IllegalStateException("recursive invocation");
//获取Launcher类
sun.misc.Launcher l = sun.misc.Launcher.getLauncher();
if (l != null) {
Throwable oops = null;
//获取对应的classLoader sun.misc.Launcher#getClassLoader 为AppClassLoader
scl = l.getClassLoader();
try {
scl = AccessController.doPrivileged(
new SystemClassLoaderAction(scl));
} catch (PrivilegedActionException pae) {
oops = pae.getCause();
if (oops instanceof InvocationTargetException) {
oops = oops.getCause();
}
}
if (oops != null) {
if (oops instanceof Error) {
throw (Error) oops;
} else {
// wrap the exception
throw new Error(oops);
}
}
}
sclSet = true;
}
}
打破双亲委派机制
尝试打破双亲委派机制,用自定义类加载器加载我们自己实现的的类
package com.zengqingfa.exercise.jvm;
import java.io.FileInputStream;
import java.lang.reflect.Method;
public class MyClassLoaderTest2 {
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;
}
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
byte[] data = loadByte(name);
//defineClass将一个字节数组转为Class对象,这个字节数组是class文件读取后最终的字节 数组。
return defineClass(name, data, 0, data.length);
} catch (Exception e) {
e.printStackTrace();
throw new ClassNotFoundException();
}
}
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
if (!name.startsWith("com.zengqingfa.exercise")) {
c = this.getParent().loadClass(name);
} else {
c = findClass(name);
}
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
// this is the defining class loader; record the stats
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 {
//初始化自定义类加载器,会先初始化父类ClassLoader,其中会把自定义类加载器的父加载 器设置为应用程序类加载器AppClassLoader
MyClassLoader classLoader = new MyClassLoader("F:/test");
//F盘创建 F:testcomzengqingfaexercisejvm 几级目录,将User类的复制类User1.class丢入该目录
Class clazz = classLoader.loadClass("com.zengqingfa.exercise.jvm.User1");
Object obj = clazz.newInstance();
Method method = clazz.getDeclaredMethod("print", null);
method.invoke(obj, null);
//如果父类appClassLoader存在该类 sun.misc.Launcher$AppClassLoader
//如果父类appClassLoader不存在该类com.zengqingfa.exercise.jvm.MyClassLoaderTest$MyClassLoader
System.out.println(clazz.getClassLoader().getClass().getName());
}
}
Tomcat打破双亲委派机制
以Tomcat类加载为例,Tomcat 如果使用默认的双亲委派类加载机制行不行? 我们思考一下:Tomcat是个web容器, 那么它要解决什么问题:
1. 一个web容器可能需要部署两个应用程序,不同的应用程序可能会依赖同一个第三方类库的 不同版本,不能要求同一个类库在同一个服务器只有一份,因此要保证每个应用程序的类库都是 独立的,保证相互隔离。
2. 部署在同一个web容器中相同的类库相同的版本可以共享。否则,如果服务器有10个应用程 序,那么要有10份相同的类库加载进虚拟机。
3. web容器也有自己依赖的类库,不能与应用程序的类库混淆。基于安全考虑,应该让容器的 类库和程序的类库隔离开来。
4. web容器要支持jsp的修改,我们知道,jsp 文件最终也是要编译成class文件才能在虚拟机中 运行,但程序运行后修改jsp已经是司空见惯的事情, web容器需要支持 jsp 修改后不用重启。
再看看我们的问题:Tomcat 如果使用默认的双亲委派类加载机制行不行? 答案是不行的。为什么?
第一个问题,如果使用默认的类加载器机制,那么是无法加载两个相同类库的不同版本的,默认 的类加器是不管你是什么版本的,只在乎你的全限定类名,并且只有一份。
第二个问题,默认的类加载器是能够实现的,因为他的职责就是保证唯一性。
第三个问题和第一个问题一样。
我们再看第四个问题,我们想我们要怎么实现jsp文件的热加载,jsp 文件其实也就是class文 件,那么如果修改了,但类名还是一样,类加载器会直接取方法区中已经存在的,修改后的jsp 是不会重新加载的。那么怎么办呢?我们可以直接卸载掉这jsp文件的类加载器,所以你应该想 到了,每个jsp文件对应一个唯一的类加载器,当一个jsp文件修改了,就直接卸载这个jsp类加载 器。重新创建类加载器,重新加载jsp文件。
Tomcat自定义加载器详解
tomcat的几个主要类加载器:
commonLoader:Tomcat最基本的类加载器,加载路径中的class可以被Tomcat容 器本身以及各个Webapp访问; catalinaLoader:Tomcat容器私有的类加载器,加载路径中的class对于Webapp不 可见;
sharedLoader:各个Webapp共享的类加载器,加载路径中的class对于所有 Webapp可见,但是对于Tomcat容器不可见;
WebappClassLoader:各个Webapp私有的类加载器,加载路径中的class只对当前 Webapp可见,比如加载war包里相关的类,每个war包应用都有自己的WebappClassLoader,实现相互隔离,比如不同war包应用引入了不同的spring版本, 这样实现就能加载各自的spring版本;
从图中的委派关系中可以看出: CommonClassLoader能加载的类都可以被CatalinaClassLoader和SharedClassLoader使用, 从而实现了公有类库的共用,而CatalinaClassLoader和SharedClassLoader自己能加载的类则 与对方相互隔离。 WebAppClassLoader可以使用SharedClassLoader加载到的类,但各个WebAppClassLoader 实例之间相互隔离。 而JasperLoader的加载范围仅仅是这个JSP文件所编译出来的那一个.Class文件,它出现的目的 就是为了被丢弃:当Web容器检测到JSP文件被修改时,会替换掉目前的JasperLoader的实例, 并通过再建立一个新的Jsp类加载器来实现JSP文件的热加载功能。
模拟tomcat打破双亲委派
com.zengqingfa.exercise.jvm.User1版本一:
public class User1 {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public void print() {
System.out.println("版本一:自定义类加载器加载");
}
}
com.zengqingfa.exercise.jvm.User1版本二:
public class User1 {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public void print() {
System.out.println("版本二:自定义类加载器加载");
}
}
分别在F盘创建不同的文件夹但是相同的类路径和类名
自定义类加载器
package com.zengqingfa.exercise.jvm;
import java.io.FileInputStream;
import java.lang.reflect.Method;
public class MyClassLoaderTest3 {
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;
}
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
byte[] data = loadByte(name);
//defineClass将一个字节数组转为Class对象,这个字节数组是class文件读取后最终的字节 数组。
return defineClass(name, data, 0, data.length);
} catch (Exception e) {
e.printStackTrace();
throw new ClassNotFoundException();
}
}
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
if (!name.startsWith("com.zengqingfa.exercise")) {
c = this.getParent().loadClass(name);
} else {
c = findClass(name);
}
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
// this is the defining class loader; record the stats
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("F:/test1");
Class clazz = classLoader1.loadClass("com.zengqingfa.exercise.jvm.User1");
Object obj1 = clazz.newInstance();
Method method1 = clazz.getDeclaredMethod("print", null);
method1.invoke(obj1, null);
System.out.println(clazz.getClassLoader().getClass().getName());
System.out.println("========================");
MyClassLoader classLoader2 = new MyClassLoader("F:/test2");
Class clazz2 = classLoader2.loadClass("com.zengqingfa.exercise.jvm.User1");
Object obj2 = clazz2.newInstance();
Method method2 = clazz2.getDeclaredMethod("print", null);
method2.invoke(obj2, null);
System.out.println(clazz.getClassLoader().getClass().getName());
}
}
打印结果:
版本一:自定义类加载器加载
com.zengqingfa.exercise.jvm.MyClassLoaderTest3$MyClassLoader
========================
版本二:自定义类加载器加载
com.zengqingfa.exercise.jvm.MyClassLoaderTest3$MyClassLoader
最后
以上就是风中乐曲为你收集整理的01-jvm类加载机制的全部内容,希望文章能够帮你解决01-jvm类加载机制所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复