关于JVM类加载机制

  • 一、Java指令运行程序流程
  • 二、loadClass类加载过程
  • 三、类加载器和双亲委派机制
    • 类加载器初始化过程
    • 双亲委派机制
    • LoadClass逻辑:
    • 设计双亲委派机制用意:
    • 全盘负责委托机制:
  • 四、自定义类加载器:
  • 五、打破双亲委派机制
  • 六、Tomcat打破双亲委派机制
    • Tomcat是个web容器,需要解决以下几个问题:
    • Tomcat若使用默认的双亲委派机制无法解决上述问题:
    • Tomcat核心的类加载器:
    • Tomcat几个类加载器的委派关系:
    • 模拟实现Tomcat的WebAppClassLoader加载自己war包应用内不同版本类实现互相隔离共存的效果:

一、Java指令运行程序流程

  1. 首先通过类加载器将字节码文件加载到JVM虚拟机中,以自定义Math类为例:java com.autumn.Math.class
public class Math {public static int num = 123;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) {Math math = new Math();math.compute();}
}
  1. 以Windows系统为例,java指令即为C++语言编写的java.exe,它会调用底层C++的库函数jvm.dll文件来创建Java的虚拟机
  2. 虚拟机会创建一个引导类加载器实例
  3. C++接着会调用Java代码创建JVM启动程序,其中一个sun.misc.Launcher会由引导类加载器负责加载创建很多Java实现的类加载器:sun.misc.Launcher.getLauncher()
  4. 再获取运行类自己的加载器ClassLoader,Math类的加载器获取结果为AppClassLoader:launcher.getClassLoader()
  5. 调用loadClass方法来加载磁盘上的字节码文件Math类:classLoader.loadClass(“com.autumn.Math.class”)
  6. 加载完成后C++调用JVM执行Math类的main方法:Math.main()
  7. 程序运行结束,JVM销毁

二、loadClass类加载过程

  1. 加载:加载磁盘里的字节码文件,即target目录下的文件,使用到对应的类时才会加载,例如调用main方法,创建对象等,在内存中生成这个类的java.lang.Class对象,作为方法区这个类各种数据的访问入口

字节码文件默认开头:cafe babe

  1. 验证:验证字节码文件是否格式正确
  2. 准备:为静态变量做初始值赋值

例如:int初始值为0,boolean初始值为false,对象为null

  1. 解析:将符号引用替换为直接引用,main方法和一些静态方法替换为指向数据所存内存的指针或句柄,即为静态链接的过程,在类加载期间完成

符号:指代码里的修饰符、返回值、方法名、参数、类名等
静态链接:当这些符号加载到内存时所对应的地址
动态链接:加载时候不一定会解析成内存地址,直到运行到的时候才解析

将字节码文件分解成可读性更好的格式的指令:javap -v Math.class
生成的文件里面#数字所对应的字段即为符号

Classfile /D:/SelfProject/demo/target/classes/com/autumn/jvm/Math.classLast modified 2022-10-1; size 753 bytesMD5 checksum 7fa2b091a0b6bbf85f53b67854dca927Compiled from "Math.java"
public class com.autumn.jvm.Mathminor version: 0major version: 52flags: ACC_PUBLIC, ACC_SUPER
Constant pool:#1 = Methodref          #9.#34         // java/lang/Object."<init>":()V#2 = Class              #35            // com/autumn/jvm/Math#3 = Methodref          #2.#34         // com/autumn/jvm/Math."<init>":()V#4 = Methodref          #2.#36         // com/autumn/jvm/Math.compute:()I#5 = Fieldref           #2.#37         // com/autumn/jvm/Math.num:I#6 = Class              #38            // com/autumn/jvm/User#7 = Methodref          #6.#34         // com/autumn/jvm/User."<init>":()V#8 = Fieldref           #2.#39         // com/autumn/jvm/Math.user:Lcom/autumn/jvm/User;#9 = Class              #40            // java/lang/Object#10 = Utf8               num#11 = Utf8               I#12 = Utf8               user#13 = Utf8               Lcom/autumn/jvm/User;#14 = Utf8               <init>#15 = Utf8               ()V#16 = Utf8               Code#17 = Utf8               LineNumberTable#18 = Utf8               LocalVariableTable#19 = Utf8               this#20 = Utf8               Lcom/autumn/jvm/Math;#21 = Utf8               compute#22 = Utf8               ()I#23 = Utf8               a#24 = Utf8               b#25 = Utf8               c#26 = Utf8               main#27 = Utf8               ([Ljava/lang/String;)V#28 = Utf8               args#29 = Utf8               [Ljava/lang/String;#30 = Utf8               math#31 = Utf8               <clinit>#32 = Utf8               SourceFile#33 = Utf8               Math.java#34 = NameAndType        #14:#15        // "<init>":()V#35 = Utf8               com/autumn/jvm/Math#36 = NameAndType        #21:#22        // compute:()I#37 = NameAndType        #10:#11        // num:I#38 = Utf8               com/autumn/jvm/User#39 = NameAndType        #12:#13        // user:Lcom/autumn/jvm/User;#40 = Utf8               java/lang/Object
{public static int num;descriptor: Iflags: ACC_PUBLIC, ACC_STATICpublic static com.autumn.jvm.User user;descriptor: Lcom/autumn/jvm/User;flags: ACC_PUBLIC, ACC_STATICpublic com.autumn.jvm.Math();descriptor: ()Vflags: ACC_PUBLICCode:stack=1, locals=1, args_size=10: aload_01: invokespecial #1                  // Method java/lang/Object."<init>":()V4: returnLineNumberTable:line 9: 0LocalVariableTable:Start  Length  Slot  Name   Signature0       5     0  this   Lcom/autumn/jvm/Math;public int compute();descriptor: ()Iflags: ACC_PUBLICCode:stack=2, locals=4, args_size=10: iconst_11: istore_12: iconst_23: istore_24: iload_15: iload_26: iadd7: bipush        109: imul10: istore_311: iload_312: ireturnLineNumberTable:line 14: 0line 15: 2line 16: 4line 17: 11LocalVariableTable:Start  Length  Slot  Name   Signature0      13     0  this   Lcom/autumn/jvm/Math;2      11     1     a   I4       9     2     b   I11       2     3     c   Ipublic static void main(java.lang.String[]);descriptor: ([Ljava/lang/String;)Vflags: ACC_PUBLIC, ACC_STATICCode:stack=2, locals=2, args_size=10: new           #2                  // class com/autumn/jvm/Math3: dup4: invokespecial #3                  // Method "<init>":()V7: astore_18: aload_19: invokevirtual #4                  // Method compute:()I12: pop13: returnLineNumberTable:line 21: 0line 22: 8line 23: 13LocalVariableTable:Start  Length  Slot  Name   Signature0      14     0  args   [Ljava/lang/String;8       6     1  math   Lcom/autumn/jvm/Math;static {};descriptor: ()Vflags: ACC_STATICCode:stack=2, locals=0, args_size=00: bipush        1232: putstatic     #5                  // Field num:I5: new           #6                  // class com/autumn/jvm/User8: dup9: invokespecial #7                  // Method com/autumn/jvm/User."<init>":()V12: putstatic     #8                  // Field user:Lcom/autumn/jvm/User;15: returnLineNumberTable:line 10: 0line 11: 5
}
SourceFile: "Math.java"
  1. 初始化:将静态变量初始化为指定的值,以及执行静态代码块
  2. 使用
  3. 卸载

    类被加载到方法区后主要包含:运行时常量池、类型信息、字段信息、方法信息、类加载器的引用、对应class实例的引用等信息。

类加载器的引用:这个类到类加载器实例的引用
对应class实例的引用:类加载器在加载类信息到方法区后,会创建一个对应的Class类型的对象实例(java.lang.Class对象),放到堆中,作为开发人员访问方法区中类定义的入口和切入点

类加载属于懒加载,主类在运行过程中若使用到其它类,才逐步加载这些类。
因此jar包或war包里的类并非一次性加载,而是使用时才会加载。

加载顺序:主类的静态代码块,main方法里按顺序执行到便加载,若new对象则加载,若对象声明为null不加载

三、类加载器和双亲委派机制

上述类加载过程均由类加载器实现

  1. 引导类加载器BootstrapClassLoader:加载支撑JVM运行的位于jre的lib目录下的核心类库,例如rt.jar、charset.jar等

String.class.getClassLoader()
String属于核心类库,引导类均为C++生成的对象,所以无法用Java获取,结果为null

  1. 扩展类加载器ExtClassLoader:加载支撑JVM运行的位于jre的lib目录下的ext扩展目录中的jar包

com.sun.crypto.provider.DESKeyFactory.class.getClassLoader()
结果为sun.misc.Launcher$ExtClassLoader@xxxxxxxx

  1. 应用程序类加载器AppClassLoader:加载ClassPath路径下的类包,即自己写的类

TestJDKClassLoader.class.getClassLoader()
此类为自己写的类,结果为sun.misc.Launcher$AppClassLoader@xxxxxxxx

  1. 自定义加载器:加载用户自定义路径下的类包

Launcher类为rt.jar包里最核心的类,继承URLClassLoader。
Launcher类初始化过程,调用getLacuncher():
返回launcher,launcher在静态变量里new Launcher(),即为单例对象;
Launcher类构造方法:
①定义一个扩展类加载器:Launcher.ExtClassLoader
②调用getExtClassLoader方法来获取类加载器
扩展类加载器的初始化getExtClassLoader:
经过校验,返回new Launcher.ExtClassLoader(),即初始化一个扩展类加载器;
初始化时会调用父类加载器URLClassLoader,根据传入的磁盘路径,进行文件读写,加载到内存中。
③调用应用程序类加载器的getAppClassLoader方法,将扩展类加载器作为参数
先获取环境变量:System.getProperty(“java.class.path”),经过一些操作,最终返回new Launcher.AppClassLoader(),即初始化一个应用程序类加载器;
初始化的AppClassLoader会调用父构造方法,即URLClassLoader,作用同上。

ClassLoader.getSyetemClassLoader()即为appClassLoader:系统类生成的类加载器,为应用程序类加载器

sun.misc.Launcher$AppClassLoader@xxxxxxxx
加载了环境变量下类路径里的包:System.getProperty(“java.class.path”)
虽然appClassLoader会扫描类路径里所有的包,但只会去加载target目录下的包

appClassLoader.getParent()即为extClassLoader:应用程序类加载器的上级加载器为扩展类加载器

sun.misc.Launcher$ExtClassLoader@xxxxxxxx
加载了环境变量下的ext目录:System.getProperty(“java.ext.dirs”)

extClassLoader.getParent()即为bootstrapLoader:扩展类加载器的上级加载器为引导类加载器

结果为null,实际上加载了引导类加载器的内路径包:Launcher.getBootstrapClassPath().getURLs(),结果为URL数组

System.out.println(String.class.getClassLoader());
System.out.println(com.sun.crypto.provider.DESKeyFactory.class.getClassLoader());
System.out.println(TestJDKClassLoader.class.getClassLoader());
System.out.println("----------------------------------------");ClassLoader appClassLoader = ClassLoader.getSystemClassLoader();
ClassLoader extClassLoader = appClassLoader.getParent();
ClassLoader bootstrapClassLoader = extClassLoader.getParent();
System.out.println("the bootstrapClassLoader >>> " + bootstrapClassLoader);
System.out.println("the extClassLoader >>> " + extClassLoader);
System.out.println("the appClassLoader >>> " + appClassLoader);
System.out.println("----------------------------------------");System.out.println("bootstrapClassLoader加载以下文件:");
URL[] urLs = Launcher.getBootstrapClassPath().getURLs();
for (URL url : urLs) {System.out.println(url);
}
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@33c7353a
sun.misc.Launcher$AppClassLoader@18b4aac2
----------------------------------------
the bootstrapClassLoader >>> null
the extClassLoader >>> sun.misc.Launcher$ExtClassLoader@33c7353a
the appClassLoader >>> sun.misc.Launcher$AppClassLoader@18b4aac2
----------------------------------------
bootstrapClassLoader加载以下文件:
file:/C:/Program%20Files/Java/jdk1.8.0_301/jre/lib/resources.jar
file:/C:/Program%20Files/Java/jdk1.8.0_301/jre/lib/rt.jar
file:/C:/Program%20Files/Java/jdk1.8.0_301/jre/lib/sunrsasign.jar
file:/C:/Program%20Files/Java/jdk1.8.0_301/jre/lib/jsse.jar
file:/C:/Program%20Files/Java/jdk1.8.0_301/jre/lib/jce.jar
file:/C:/Program%20Files/Java/jdk1.8.0_301/jre/lib/charsets.jar
file:/C:/Program%20Files/Java/jdk1.8.0_301/jre/lib/jfr.jar
file:/C:/Program%20Files/Java/jdk1.8.0_301/jre/classes
----------------------------------------
extClassLoader加载了以下文件:
C:\Program Files\Java\jdk1.8.0_301\jre\lib\ext;C:\Windows\Sun\Java\lib\ext
----------------------------------------
appClassLoader加载了以下文件:
C:\Program Files\Java\jdk1.8.0_301\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_301\jre\lib\deploy.jar;
C:\Program Files\Java\jdk1.8.0_301\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_301\jre\lib\ext\cldrdata.jar;
C:\Program Files\Java\jdk1.8.0_301\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_301\jre\lib\ext\jaccess.jar;
C:\Program Files\Java\jdk1.8.0_301\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_301\jre\lib\ext\localedata.jar;
C:\Program Files\Java\jdk1.8.0_301\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_301\jre\lib\ext\sunec.jar;
C:\Program Files\Java\jdk1.8.0_301\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_301\jre\lib\ext\sunmscapi.jar;
C:\Program Files\Java\jdk1.8.0_301\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_301\jre\lib\ext\zipfs.jar;
C:\Program Files\Java\jdk1.8.0_301\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_301\jre\lib\jce.jar;
C:\Program Files\Java\jdk1.8.0_301\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_301\jre\lib\jfxswt.jar;
C:\Program Files\Java\jdk1.8.0_301\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_301\jre\lib\management- agent.jar;
C:\Program Files\Java\jdk1.8.0_301\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_301\jre\lib\resources.jar;
C:\Program Files\Java\jdk1.8.0_301\jre\lib\rt.jar;D:\SelfProject\demo\target\classes;
D:\.m2\repository\org\projectlombok\lombok\1.18.24\lombok-1.18.24.jar;
D:\.m2\repository\com\google\guava\guava\21.0\guava-21.0.jar;C:\Program Files\JetBrains\IntelliJ IDEA 2021.3\lib\idea_rt.jar
*/

类加载器初始化过程

先创建JVM启动器实例sun.misc.Launcher:Launcher初始化为单例模式,保证一个JVM仅有一个Launcher实例;
Launcher构造方法内,分别创建了两个类加载器:sun.misc.Launcher.ExtClassLoader扩展类加载器、sun.misc.Launcher.AppClassLoader应用程序类加载器;
JVM默认使用Launcher.getClassLoader()返回的AppClassLoader的实例加载我们的应用程序。

public Launcher() {Launcher.ExtClassLoader var1;try{//创建扩展类加载器,并将上级加载器置为nullvar1 = Launcher.ExtClassLoader.getExtClassLoader();} catch (IOException var10) {throw new InternalError("Could not create extension class loader", var10);}try{//创建应用程序类加载器,并将上级加载器置为ExtClassLoader//Launcher的loader属性值是AppClassLoader,一般用此类加载器来加载我们自己写的应用程序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");//以下省略 ……
}

双亲委派机制

当运行类进行加载时,会先通过应用程序类加载器,若应用程序类加载器已加载的包中已包含此类,则会直接加载返回,若不存在则向上委托给扩展类加载器;
扩展类加载器已加载的包中若存在此类则加载返回,若不存在则再向上委托给引导类加载器;
引导类加载器已加载的包中若存在此类则加载返回,若不存在则引导类加载器会尝试去内路径包内寻找,若仍旧寻找不到,则向下委托给扩展类加载器;
扩展类加载器则会尝试去环境变量下ext目录下寻找,若仍旧寻找不到,则再向下委托给应用程序类加载器,应用程序类加载器则会尝试去环境变量下target目录下寻找,即loadClass方法,找到后即加载返回。
上下级类加载器非继承关系,根据parent属性来进行委托,继承自URLClassLoader,调用的是URLClassLoader的loadClass方法,URLClassLoader再调用父类的loadClass方法,直到调用到根父类ClassLoader的loadClass方法。

绝大多数类都是属于程序类加载器进行加载的,虽然第一次加载步骤繁琐,但之后再次加载时,便能在第一轮程序类加载器中直接加载返回,从而提高加载性能

LoadClass逻辑:

  1. 先检查指定名称的类是否已被当前类加载器加载过,若加载过则直接返回;
  2. 若未加载过,则判断上级类加载器是否存在,若存在则委托上级类加载器去加载,即调用parent.loadClass(name, false);
  3. 若不存在则说明已经是最上级类加载器,即引导类加载器bootstrapClassLoader,进行加载;
  4. 若上级类加载器与引导类加载器都找不到该类,则调用当前类的findClass方法来完成加载。
protected Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException
{synchronized (getClassLoadingLock(name)) {// 例如name为"com.autumn.jvm.Math"// 当前类加载器会先检查是否已加载过该类,最终会调用到C++写的本地方法来执行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) {// 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 statssun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);sun.misc.PerfCounter.getFindClasses().increment();}}if (resolve) {resolveClass(c);}return c;}
}
protected Class<?> findClass(final String name)throws ClassNotFoundException
{final Class<?> result;try {result = AccessController.doPrivileged(new PrivilegedExceptionAction<Class<?>>() {public Class<?> run() throws ClassNotFoundException {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;
}

设计双亲委派机制用意:

1. 沙箱安全机制:
自己写的类若与自带的类重名,例如java.lang.String.class,此时该类不会被加载,以此来防止核心API库被任意篡改。
2. 避免类的重复加载:
当下级类加载器已加载指定类时,就不会让上级类加载器再加载一次,以此保证被加载的类的唯一性。

public class String {public static void main(String[] args) {System.out.println("====== This is my String ======");}
}

运行时报如下错误:
错误: 在类 java.lang.String 中找不到 main 方法, 请将 main 方法定义为: public static void main(String[] args) 否则 JavaFX 应用程序类必须扩展javafx.application.Application

全盘负责委托机制:

当一个类加载器加载一个指定类时,除非该类显示地使用另一个类加载器,否则该类所有的依赖和引用都仅用此类加载器加载。

四、自定义类加载器:

  1. 需继承ClassLoader;
  2. 两大核心方法:
    ①loadClass(String, boolean),实现了双亲委派机制
    findClass(String),默认实现为空方法看,需进行重写
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;}@Overrideprotected 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,// 期间将自定义类加载器的上级类加载器设置为应用程序类加载器AppClassLoaderMyClassLoader myClassLoader = new MyClassLoader("D:/demo");// D盘创建多级目录:demo/com/autumn/jvm,将User1类放入该目录Class clazz = myClassLoader.loadClass("com.autumn.jvm.User1");Object obj = clazz.newInstance();Method method = clazz.getDeclaredMethod("sout", null);method.invoke(obj, null);System.out.println(clazz.getClassLoader().getClass().getName());}}
}
package com.autumn.jvm;public class User {private int id;private String name;static {System.out.println("====== load User ======");}public User() {}public User(int id, String name) {super();this.id = id;this.name = name;}public int getId() {return id;}public void setId(int id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}public void sout(){System.out.println("自定义加载器加载类调用方法");}
}

当项目中有User1.class时,说明应用程序类加载器AppClassLoader已经加载过此类了,所以在加载D盘的User1.class时,可在AppClassLoader找到,并直接返回,因此运行结果如下:

====== load User ======
------ 自定义加载器加载类调用方法 ------
sun.misc.Launcher$AppClassLoader

当项目中没有User1.class时,说明AppClassLoader没有加载过此类,上级类加载器也更不会有,因此最终又会向下委托,直到自定义加载器自行加载,因此运行结果如下:

====== load User ======
------ 自定义加载器加载类调用方法 ------
com.autumn.jvm.MyClassLoaderTest$MyClassLoader

五、打破双亲委派机制

用自定义类加载器加载指定的类,当项目存在该类时也不委托给应用程序类加载器,因此要重写loadClass方法

@Override
protected Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException {synchronized (getClassLoadingLock(name)) {// First, check if the class has already been loadedClass<?> 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) {// 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();// 加载时需加载核心类Object,而核心类只能由引导类加载器来加载// 当要加载的类不是自己写的类时,向上委托,否则直接加载,即完成了打破双亲委派if (!name.startsWith("com.autumn.jvm")) {c = this.getParent().loadClass(name);} else {c = findClass(name);}// this is the defining class loader; record the statssun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);sun.misc.PerfCounter.getFindClasses().increment();}}if (resolve) {resolveClass(c);}return c;}
}

执行结果:

====== load User ======
------ 自定义加载器加载类调用方法 ------
com.autumn.jvm.MyClassLoaderTest$MyClassLoader

六、Tomcat打破双亲委派机制

Tomcat是个web容器,需要解决以下几个问题:

  1. 一个web容器可能需部署两个应用程序,不同的应用程序可能会依赖同一个第三方类库的不同版本,不能要求同一个类库在同一个服务器只有一份,需要保证每个应用程序的类库互相独立、互相隔离。
  2. 部署在同一个web容器中相同的类库相同的版本可以共享,否则若服务器有多个应用程序还要多个相同类库加载进虚拟机。
  3. web容器也有自己依赖的类库,不能与应用程序的类库混淆。将容器的类库和应用程序的类库隔离开来保证安全。
  4. web容器需支持jsp的修改,因为jsp文件最终也是编译成class文件加载进虚拟机中运行,但程序运行后修改jsp是司空见惯的事,因此web容器需支持jsp修改后不用重启。

Tomcat若使用默认的双亲委派机制无法解决上述问题:

  1. 双亲委派无法加载两个相同类库的不同版本,默认的类加载器不论什么版本,只保存一份全限定类名。
  2. 默认的类加载器可以实现同一个web容器中共享相同的类库相同的版本,其职责就是确保唯一性
  3. 原因同1,无法解决第3个问题。
  4. 若要web容器支持jsp修改后不重启,就需实现jsp文件的热加载,即每个jsp文件对应一个唯一的类加载器,当一个jsp文件修改了,就直接卸载该jsp的类加载器,重新创建新的类加载器,加载jsp文件。

Tomcat核心的类加载器:

  1. CommonClassLoader:Tomcat最基本的类加载器,加载路径中的class可以被Tomcat容器本身及各个Webapp访问。
  2. CatalinaClassLoader:Tomcat容器私有的类加载器,加载路径中的class对Webapp不可见。
  3. SharedClassLoader:各个WebApp共享的类加载器,加载路径中的class对所有Webapp可见,但对Tomcat容器本身不可见。
  4. WebAppClassLoader:各个WebApp私有的类加载器,加载路径中的class只对当前WebApp可见,比如加载war包里相关的类,每个war包应用都有各自独立的WebAppClassLoader,相互隔离。

当不同war包引入不同的spring版本时,即可实现各自加载各自的spring版本。

Tomcat几个类加载器的委派关系:

  1. CommonClassLoader能加载的类都可以被CatalinaClassLoader和SharedClassLoader使用,从而实现公有类库的共用,而CatalinaClassLoader和SharedClassLoader自己加载的类则互相隔离。
  2. WebAppClassLoader可以使用SharedClassLoader加载到的类,但各个WebAppClassLoader的实例互相隔离。
  3. JasperLoader的加载范围仅限于此jsp文件所编译出来的class文件,当Web容器检测到jsp文件被修改时,会立即丢弃目前的JasperLoader实例,重新创建一个新的进行加载,以此来实现jsp文件的热加载功能。

Tomcat每个WebAppClassLoader加载自己目录下的class文件,不会传递给上级类加载器,打破双亲委派机制。

模拟实现Tomcat的WebAppClassLoader加载自己war包应用内不同版本类实现互相隔离共存的效果:

复制一份User1,将sout方法里的打印内容做区分,放在demo1目录下,在前面已经打破双亲委派机制的自定义类加载器中创建新的实例加载demo1目录下的同一个类名

public static void main(String[] args) throws Exception {MyClassLoader myClassLoader = new MyClassLoader("D:/demo");Class clazz = myClassLoader.loadClass("com.autumn.jvm.User1");Object obj = clazz.newInstance();Method method = clazz.getDeclaredMethod("sout", null);method.invoke(obj, null);System.out.println(clazz.getClassLoader().getClass().getName());MyClassLoader myClassLoader1 = new MyClassLoader("D:/demo1");Class clazz1 = myClassLoader1.loadClass("com.autumn.jvm.User1");Object obj1 = clazz1.newInstance();Method method1 = clazz1.getDeclaredMethod("sout", null);method1.invoke(obj1, null);System.out.println(clazz1.getClassLoader().getClass().getName());
}

执行结果:

====== load User ======
------ 自定义加载器加载类调用方法 ------
com.autumn.jvm.MyClassLoaderTest$MyClassLoader
====== load User ======
------ 另一个版本的User1:自定义加载器加载类调用方法 ------
com.autumn.jvm.MyClassLoaderTest$MyClassLoader

同一个JVM内,两个相同包名和类名的类对象可以共存,因为他们的类加载器可以不一样。 因此要判断是否为同一个类对象的话,除了看类的包名和类名是否相同外,还需要看他们的类加载器是否为同一个。

关于JVM类加载机制相关推荐

  1. JVM基础系列第7讲:JVM 类加载机制

    当 Java 虚拟机将 Java 源码编译为字节码之后,虚拟机便可以将字节码读取进内存,从而进行解析.运行等整个过程,这个过程我们叫:Java 虚拟机的类加载机制.JVM 虚拟机执行 class 字节 ...

  2. jvm类加载机制_面试:对于JVM类加载机制深度解析

    目录: 前文回顾 JVM在什么情况下会加载一个类? 从实用角度出发,来看看验证.准备和初始化的过程 核心阶段:初始化 类加载器和双亲委派机制 1.前文回顾 咱们今天先来回顾一下昨天讲到的JVM整体的一 ...

  3. 【Java虚拟机规范】JVM类加载机制

    [Java虚拟机规范]JVM类加载机制 理论知识 一个类型从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期将会经历加载(Loading).验证(Verification).准备(Prep ...

  4. jvm类加载机制_JVM 类加载机制

    学习导图 一.为什么要学习类加载机制? 今天想跟大家唠嗑唠嗑 Java 的类加载机制,这是 Java 的一个很重要的创新点,曾经也是 Java 流行的重要原因之一. Oracle 当初引入这个机制是为 ...

  5. JVM类加载机制(ClassLoader)源码解析

    http://blog.csdn.net/chenyi8888/article/details/7066569 其实JVM类加载机制,简单地说就是类管理,也就是我们生成的class文件. 三个步骤:装 ...

  6. JVM(2)——JVM类加载机制

    一.JVM类加载机制简介 虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验.转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制. 在Java语言里面 ...

  7. jvm类加载机制和类加载器_在JVM之下–类加载器

    jvm类加载机制和类加载器 在许多开发人员中,类加载器是Java语言的底层,并且经常被忽略. 在ZeroTurnaround上 ,我们的开发人员必须生活,呼吸,饮食,喝酒,并且几乎与类加载器保持亲密关 ...

  8. JVM类加载机制详解

    本文来详细说下JVM类加载机制 文章目录 概述 类加载器是什么 类加载的过程 加载 连接 初始化 类的主动引用和被动引用 主动引用 被动引用 三种类加载器 双亲委托机制 核心思想 源码分析 类的动态加 ...

  9. 详解JVM类加载机制

    详解JVM类加载机制 笔者的笔记都记录在有道云里面,因为公司原因办公电脑无法使用有道云,正好借此机会整理下以前的笔记顺便当做巩固复习了,也因为记笔记的时候不会记录这些知识来源何地,所以如果发现原创后可 ...

  10. JVM类加载机制详解-20160812

    JVM类加载机制 一,类加载器体系     类加载器是沙箱的第一道防线,毕竟代码都是类加载器装入到JVM的.类加载体系通过使用不同的类加载器把类放 入不同的命名空间中,从而保护善意代码不受恶意代码的干 ...

最新文章

  1. ubuntu16.04分区
  2. cpu序列号能告诉别人嘛_微信这个开关不删除,别人手机能随意登录你的微信,学会告诉家人...
  3. 用jk触发器构成二分频电路_深入了解数字电路之时序电路
  4. Celery介绍和使用
  5. SQL server挂了之后
  6. jQuery学习(十一)— 常用的删除方法
  7. android测试测试什么,1,web测试,Android测试,Ios测试的共同点与
  8. hive0.12安装
  9. 作为一个程序员,数学对你到底有多重要
  10. matlab控制读取数小数位,matlab输出的数值型矩阵中如何控制小数位数,以及对齐方式?...
  11. 究竟什么是软件资产管理(SAM)?企业应该怎么做?
  12. 美通社日历 | 媒体关注、会展信息、企业财报发布,节假日备忘(1月11日—1月17日)...
  13. Teams 的 Meeting App
  14. OrCAD多页原理图器件按页编号的设置
  15. c语言win32api勾取,第一次用C语言+win32api写窗体应用程序,晕死了
  16. 百度android离线下载,离线宝app下载-百度离线宝 安卓版v1.0.0.0-PC6安卓网
  17. IB学校书单合集请收藏好
  18. 快速下载||AnotherRedisDesktopManagerMedis-Redis可视化工具
  19. 离谱,还有这种自暴自弃的翻译?
  20. slurm学习笔记(一)

热门文章

  1. i5 3470+XSB75M-PK+HD 7750安装黑苹果macOS Big Sur 11.7.7
  2. 预览窗格无法预览word_使用Word 2010中的导航窗格轻松重组文档
  3. HashMap底层红黑树原理(超详细图解)+手写红黑树代码
  4. Visual Studio 滚动条略缩图设置
  5. iPhone“特价版”便宜2000多元:但代购党要哭了
  6. 第三十八章 变态级怪物
  7. 查看后台进程并杀死进程
  8. css position fixed 居中,css中position:fixed实现div居中上下左右居中
  9. EF框架之Code First
  10. 15.transformer全解