java基础之类运行与双亲委派机制简介

news/2024/10/22 10:55:34/

一 类加载运行过程

通过java命令运行某个类的main函数来启动程序时,首先需要通过类加载器将主类加载到JVM中;

源码:

java">package com.ddu.jvm;public class HelloWordHelper {public static void main(String[] args) {User user = new User();user.setAge(1);user.setName("1岁");add(1, 2);}private static int add(int a, int b) {return a + b;}static class User {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;}}
}

 通过Java命令执行代码的大体过程如下:

 其中loadClass的类加载过程有如下几步:

加载=>验证=>准备=>解析=>初始化=>使用=>卸载

  • 加载:在本地硬盘或者网络资源上,通过IO读取字节码文件,使用到类时才会加载,例如调用类的main()方法,new对象等等,在加载阶段会在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口;
  • 验证:验证字节码的正确性;
  • 准备:给类的静态变量分配内存,并赋予默认值;
  • 解析:符号引用替换为直接引用,该阶段会把一些静态方法(符号引用,比如main()方法)替换指向数据锁在内存的指针或句柄等(即直接引用),这就是所谓的静态链接(类加载期间完成),动态链接是在程序运行期间完成的,将符号引用替换为直接引用;
  • 初始化:针对类的静态变量初始化为指定的值,执行静态代码块;

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

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

其中,主类在运行过程中如果需要使用到其他类,会逐步加载这些类;即jar包或war包里的类不是一次性全部加载的,是使用到时才会加载;

源码:

java">package com.ddu.jvm;public class HelloWordHelper {static {System.out.println("load HelloWordHelper...");}public static void main(String[] args) {
//        User user = new User();
//        user.setAge(1);
//        user.setName("1岁");
//        add(1, 2);new Order();System.out.println("load HelloWordHelper");// Address不会加载,除非这里执行new Address();Address address = null;}private static int add(int a, int b) {return a + b;}static class Order{static {System.out.println("loader Order ......");}public Order() {System.out.println("initial Order ... ");}}static class Address{static {System.out.println("loader Address ......");}public Address() {System.out.println("initial Address ... ");}}static class User {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;}}
}

运行结果:

java">load HelloWordHelper...
loader Order ......
initial Order ... 
load HelloWordHelper

二 类加载器和双亲委派机制

上面的类加载过程主要是通过类加载来实现的,Java中有如下几种类加载器:

  • 引导类加载器:负责加载支撑JVM运行的位于JRE安装目录lib目录下的核心类库,比如rt.jar、charsets.jar
  • 扩展类加载器:负载加载支持JVM运行的位于JRE的lib目录下的ext扩展目录中的JAR包;
  • 应用程序类加载器:负载加载ClassPath路径下的类包,主要是加载开发人员自己开发的类;
  • 自定义类加载器:负责加载用户自定义路径下的类包;

类加载器示例:

java">package com.ddu.jvm;import sun.misc.Launcher;import java.net.URL;public class DduClassLoaderHelper {public static void main(String[] args) {// 预期是null,因为String是引导类加载器加载的,而引导类加载器是C++实现的,JVM内存中没有C++的实例System.out.println(String.class.getClassLoader());// DESKeyFactory类是JRE/lib/ext包下的,类加载器为ExtClassLoaderSystem.out.println(com.sun.crypto.provider.DESKeyFactory.class.getClassLoader().getClass().getName());// 业务自己定义的类,类加载器为AppClassLoaderSystem.out.println(DduClassLoaderHelper.class.getClassLoader().getClass().getName());System.out.println("---------------------------------");// 系统类加载器,即为AppClassLoaderClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();// 系统类加载器的父加载器为扩展类加载器=>ExtClassLoaderClassLoader extClassLoader = systemClassLoader.getParent();// 扩展类加载器的父类加载器为系引导类加载器=>BootstrapClassLoaderClassLoader bootStrapClassLoader = extClassLoader.getParent();System.out.println(systemClassLoader);System.out.println(extClassLoader);System.out.println(bootStrapClassLoader);System.out.println("--------BootstrapClassLoader加载如下的class文件-----------------------");URL[] urLs = Launcher.getBootstrapClassPath().getURLs();for (URL urL : urLs) {System.out.println(urL);}System.out.println("----------ExtClassLoader加载如下目录的class文件---------------------");System.out.println(System.getProperty("java.ext.dirs"));System.out.println("----------AppClassLoader加载如下目录的class文件---------------------");System.out.println(System.getProperty("java.class.path"));}
}

运行结果:

java">null
sun.misc.Launcher$ExtClassLoader
sun.misc.Launcher$AppClassLoader
---------------------------------
sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@506e1b77
null
--------BootstrapClassLoader加载如下的class文件-----------------------
file:/C:/Program%20Files/Java/jdk1.8.0_333/jre/lib/resources.jar
file:/C:/Program%20Files/Java/jdk1.8.0_333/jre/lib/rt.jar
file:/C:/Program%20Files/Java/jdk1.8.0_333/jre/lib/sunrsasign.jar
file:/C:/Program%20Files/Java/jdk1.8.0_333/jre/lib/jsse.jar
file:/C:/Program%20Files/Java/jdk1.8.0_333/jre/lib/jce.jar
file:/C:/Program%20Files/Java/jdk1.8.0_333/jre/lib/charsets.jar
file:/C:/Program%20Files/Java/jdk1.8.0_333/jre/lib/jfr.jar
file:/C:/Program%20Files/Java/jdk1.8.0_333/jre/classes
----------ExtClassLoader加载如下目录的class文件---------------------
C:\Program Files\Java\jdk1.8.0_333\jre\lib\ext;C:\Windows\Sun\Java\lib\ext
----------AppClassLoader加载如下目录的class文件---------------------
C:\Program Files\Java\jdk1.8.0_333\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_333\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_333\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_333\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_333\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_333\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_333\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_333\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_333\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_333\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_333\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_333\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_333\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_333\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_333\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_333\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_333\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_333\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_333\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_333\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_333\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_333\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_333\jre\lib\rt.jar;D:\project\ddu-base\ddu-base\ddu-java\target\classes;D:\repository\org\springframework\boot\spring-boot-starter\2.1.4.RELEASE\spring-boot-starter-2.1.4.RELEASE.jar;D:\repository\org\springframework\boot\spring-boot\2.1.4.RELEASE\spring-boot-2.1.4.RELEASE.jar;D:\repository\org\springframework\spring-context\5.1.6.RELEASE\spring-context-5.1.6.RELEASE.jar;D:\repository\org\springframework\spring-aop\5.1.6.RELEASE\spring-aop-5.1.6.RELEASE.jar;D:\repository\org\springframework\spring-beans\5.1.6.RELEASE\spring-beans-5.1.6.RELEASE.jar;D:\repository\org\springframework\spring-expression\5.1.6.RELEASE\spring-expression-5.1.6.RELEASE.jar;D:\repository\org\springframework\boot\spring-boot-autoconfigure\2.1.4.RELEASE\spring-boot-autoconfigure-2.1.4.RELEASE.jar;D:\repository\org\springframework\boot\spring-boot-starter-logging\2.1.4.RELEASE\spring-boot-starter-logging-2.1.4.RELEASE.jar;D:\repository\ch\qos\logback\logback-classic\1.2.3\logback-classic-1.2.3.jar;D:\repository\ch\qos\logback\logback-core\1.2.3\logback-core-1.2.3.jar;D:\repository\org\apache\logging\log4j\log4j-to-slf4j\2.11.2\log4j-to-slf4j-2.11.2.jar;D:\repository\org\apache\logging\log4j\log4j-api\2.11.2\log4j-api-2.11.2.jar;D:\repository\org\slf4j\jul-to-slf4j\1.7.26\jul-to-slf4j-1.7.26.jar;D:\repository\javax\annotation\javax.annotation-api\1.3.2\javax.annotation-api-1.3.2.jar;D:\repository\org\springframework\spring-core\5.1.6.RELEASE\spring-core-5.1.6.RELEASE.jar;D:\repository\org\springframework\spring-jcl\5.1.6.RELEASE\spring-jcl-5.1.6.RELEASE.jar;D:\repository\org\yaml\snakeyaml\1.23\snakeyaml-1.23.jar;D:\repository\org\springframework\boot\spring-boot-starter-test\2.1.4.RELEASE\spring-boot-starter-test-2.1.4.RELEASE.jar;D:\repository\org\springframework\boot\spring-boot-test\2.1.4.RELEASE\spring-boot-test-2.1.4.RELEASE.jar;D:\repository\org\springframework\boot\spring-boot-test-autoconfigure\2.1.4.RELEASE\spring-boot-test-autoconfigure-2.1.4.RELEASE.jar;D:\repository\com\jayway\jsonpath\json-path\2.4.0\json-path-2.4.0.jar;D:\repository\net\minidev\json-smart\2.3\json-smart-2.3.jar;D:\repository\net\minidev\accessors-smart\1.2\accessors-smart-1.2.jar;D:\repository\org\ow2\asm\asm\5.0.4\asm-5.0.4.jar;D:\repository\org\slf4j\slf4j-api\1.7.26\slf4j-api-1.7.26.jar;D:\repository\junit\junit\4.12\junit-4.12.jar;D:\repository\org\assertj\assertj-core\3.11.1\assertj-core-3.11.1.jar;D:\repository\org\mockito\mockito-core\2.23.4\mockito-core-2.23.4.jar;D:\repository\net\bytebuddy\byte-buddy\1.9.12\byte-buddy-1.9.12.jar;D:\repository\net\bytebuddy\byte-buddy-agent\1.9.12\byte-buddy-agent-1.9.12.jar;D:\repository\org\objenesis\objenesis\2.6\objenesis-2.6.jar;D:\repository\org\hamcrest\hamcrest-core\1.3\hamcrest-core-1.3.jar;D:\repository\org\hamcrest\hamcrest-library\1.3\hamcrest-library-1.3.jar;D:\repository\org\skyscreamer\jsonassert\1.5.0\jsonassert-1.5.0.jar;D:\repository\com\vaadin\external\google\android-json\0.0.20131108.vaadin1\android-json-0.0.20131108.vaadin1.jar;D:\repository\org\springframework\spring-test\5.1.6.RELEASE\spring-test-5.1.6.RELEASE.jar;D:\repository\org\xmlunit\xmlunit-core\2.6.2\xmlunit-core-2.6.2.jar;D:\repository\org\projectlombok\lombok\1.18.22\lombok-1.18.22.jar;D:\soft\Intelijidea\lib\idea_rt.jar

2.1 类加载器初始化过程

类运行加载过程中,会创建JVM启动器实例sun.misc.Launcher,在Launcher构造方法内部,创建了两个类加载器,分别是sun.misc.Launcher.ExtClassLoader(扩展类加载器)和sun.misc.Launcher.AppClassLoader(应用类加载器),JVM默认使用Launcher的getClassLoader()方法返回的类加载器(即为AppClassLoader)加载我们的应用程序;

以下是sun.misc.Launcher部分源码:

java">public class Launcher {private static URLStreamHandlerFactory factory = new 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() {ExtClassLoader var1;try {// 扩展类加载器,在构造当前类加载器时,将其父加载器设置为nullvar1 = Launcher.ExtClassLoader.getExtClassLoader();} catch (IOException var10) {throw new InternalError("Could not create extension class loader", var10);}try {// 构造应用类加载器,在构造过程中,将器父类加载器设置为ExtClassLoaderthis.loader = Launcher.AppClassLoader.getAppClassLoader(var1);} catch (IOException var9) {throw new InternalError("Could not create application class loader", var9);}Thread.currentThread().setContextClassLoader(this.loader);省略其他代码public ClassLoader getClassLoader() {return this.loader;
}    

Launcher.ExtClassLoader.getExtClassLoader()实现:

java">static class ExtClassLoader extends URLClassLoader {private static volatile ExtClassLoader instance;public static ExtClassLoader getExtClassLoader() throws IOException {if (instance == null) {Class var0 = ExtClassLoader.class;synchronized(ExtClassLoader.class) {if (instance == null) {instance = createExtClassLoader();}}}return instance;}private static ExtClassLoader createExtClassLoader() throws IOException {try {return (ExtClassLoader)AccessController.doPrivileged(new PrivilegedExceptionAction<ExtClassLoader>() {public ExtClassLoader run() throws IOException {File[] var1 = Launcher.ExtClassLoader.getExtDirs();int var2 = var1.length;for(int var3 = 0; var3 < var2; ++var3) {MetaIndex.registerDirectory(var1[var3]);}return new ExtClassLoader(var1);}});} catch (PrivilegedActionException var1) {throw (IOException)var1.getException();}}void addExtURL(URL var1) {super.addURL(var1);}public ExtClassLoader(File[] var1) throws IOException {// 此处设置当前类加载器的父类加载器为nullsuper(getExtURLs(var1), (ClassLoader)null, Launcher.factory);SharedSecrets.getJavaNetAccess().getURLClassPath(this).initLookupCache(this);}
Launcher.AppClassLoader.getAppClassLoader(extClassLoader)实现源码
java">static class AppClassLoader extends URLClassLoader {final URLClassPath ucp = SharedSecrets.getJavaNetAccess().getURLClassPath(this);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<AppClassLoader>() {public AppClassLoader run() {URL[] var1x = var1 == null ? new URL[0] : Launcher.pathToURLs(var2);return new AppClassLoader(var1x, var0);}});}AppClassLoader(URL[] var1, ClassLoader var2) {// 此处var2即为设置父类加载器super(var1, var2, Launcher.factory);this.ucp.initLookupCache(this);}
省略其他代码

2.2 双亲委派机制

JVM类加载是有亲子层级结构的,如下图 :

类加载其实就是一个双亲委派机制,加载某个类时,会先委托给父类加载器寻找加载目标类,父类加载器会继续委托给自己的父类加载器,直到当前类加载器的父类加载器为null(实际是C++实现的bootstapClassLoader)时,开始尝试加载当前类,如果所有的父类加载器都无法加载到当前类,则在自己的类加载路径加载当前类;

例如:HelloWordHelper类,

  • 逐层委托:首先会找应用类加载器加载,应用类加载器则会先委托给扩展类加载器加载,扩展类加载器会先委托给引导类加载器;
  • 自顶向下开始尝试加载目标类:
    • BootstapClassLoader加载不到HelloWordHelper,则退回至ExtClassLoader加载目标类;
    • ExtClassLoader加载不到目标类HelloWordHelper,则退回至AppClassLoader加载器尝试加载目标类;
    • AppClassLoader在class.path目录下加载到目标类,完成目标类加载;

简单点说:双亲委派机制就是儿子啃老,有事情先找老爹,老爹有事情先找爷爷,爷爷办不到老爹自己想办法,老爹实在办不到就儿子自己想办法了;

2.3 AppClassLoader源码是如何完成双亲委派机制的?

源码:

2.3.1 AppclassLoader实现了loadClass方法

最终会调用ClassLoader.loadClass方法

java">static class AppClassLoader extends URLClassLoader {public Class<?> loadClass(String var1, boolean var2) throws ClassNotFoundException {int var3 = var1.lastIndexOf(46);if (var3 != -1) {SecurityManager var4 = System.getSecurityManager();if (var4 != null) {var4.checkPackageAccess(var1.substring(0, var3));}}if (this.ucp.knownToNotExist(var1)) {Class var5 = this.findLoadedClass(var1);if (var5 != null) {if (var2) {this.resolveClass(var5);}return var5;} else {throw new ClassNotFoundException(var1);}} else {// ClassLoader中实现了双亲委派机制return super.loadClass(var1, var2);}}
}

2.3.2 ClassLoader.loadClass方法实现双亲委派机制 

java">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);// 如果当前类没有父类加载器,则委托给BootstapClassLoader尝试加载,最终调用native方法} 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;}}

2.4 为什么要设计双亲委派机制?

  • 沙箱安全机制:研发人员自己开发的java.lang.String.class类不会被加载,防止核心API库被随意修改;
  • 避免类的重复加载:当父类已经加载过该类时,就没必要子ClassLoader再加载一次,保证被加载类的唯一性;
    • 被加载类唯一性:加载器类+目标类

示例:

java">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

2.5 委托机制的传播性

当一个ClassLoader加载一个类时,除非显示的使用另外一个ClassLoader,否则该类所依赖及引用的类也由这个ClassLoader载入;

2.6 自定义类加载器

自定义类加载器只需要加成java.lang.ClassLoader类,该类有两个核心方法:

  • loadClass(String,boolean):实现了双亲委派机制;
  • findClass:默认是空方法,自己类加载器时,主要是重写findClass方法;

示例:

java">package com.ddu.jvm;import java.io.FileInputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Objects;/*** 自定义类加载器*/
public class DduClassLoaderHelper {public static void main(String[] args) {// d://uclazz/com/ddu/jvm/Order1.classDduClassLoader dduClassLoader = new DduClassLoader("D:/uclazz");try {Class<?> clazz = dduClassLoader.loadClass("com.ddu.jvm.Order1");Object order = clazz.newInstance();Method method = clazz.getDeclaredMethod("print", null);method.invoke(order, null);System.out.println(clazz.getClassLoader().getClass().getName());} catch (ClassNotFoundException e) {throw new RuntimeException(e);} catch (NoSuchMethodException e) {throw new RuntimeException(e);} catch (InvocationTargetException e) {throw new RuntimeException(e);} catch (IllegalAccessException e) {throw new RuntimeException(e);} catch (InstantiationException e) {throw new RuntimeException(e);}}static class DduClassLoader extends ClassLoader {private String classPath;public DduClassLoader(String classPath) {this.classPath = classPath;}private byte[] loadByte(String name) throws Exception {name = name.replaceAll("\\.", "/");FileInputStream fis = null;byte[] data = null;try {// d://uclazz/com/ddu/jvm/Order1.classfis = new FileInputStream(classPath + "/" + name + ".class");int length = fis.available();data = new byte[length];fis.read(data);} catch (Exception e) {throw new RuntimeException("加载class文件异常!");} finally {if (Objects.nonNull(fis)) {fis.close();}}return data;}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);}}}
}

Order1源码:

放置目录:D:\uclazz\com\ddu\jvm

自己javac Order1.java

java">package com.ddu.jvm;import java.io.File;public class Order1 {public void print(){File file = new File("");System.out.println("d: uclazz order load path:"+file.getAbsoluteFile());}
}

运行结果:

java">d: uclazz order load path:D:\project\ddu-base\ddu-base
com.ddu.jvm.DduClassLoaderHelper$DduClassLoader

2.7 tomcat是如何打破双亲委派机制的?

2.7.1 tomcat作为web容器,需要解决什么问题?

  1. 一个web容器可能需要部署多个应用程序,不同的应用程序可能会依赖同一个三方包的不同版本,因此需要保证每个应用程序的依赖类库都是独立的,保证相互隔离;
  2. 部署在同一个web容器中的相同类库相同的版本可以共享,否则如果一个web容器下部署了n个应用程序,那么要用n份相同的依赖类库加载到虚拟机;
  3. web容器本身也需要依赖三方类库,容器依赖的三方类库与应用程序的依赖三方类库不能混淆,基于安全考虑,应该让容器的类库和程序的类库隔离开来;
  4. web容器要支持jsp的修改,而jsp文件最终是要编译成class文件才能在虚拟机中运行,但程序运行后修改jsp已经是很常见的事情,web容器需要支持jsp修改后不重启就可以生效;也就是大家常说的:jsp热加载;

2.7.2 tomcat如果使用默认的双亲委派加载机制行不行?

  • 答案:不行;
  • 原因:
  1. 问题1,如果使用默认的类加载器机制,那么是无法加载两个相同类库的不同版本的,同一个类全限定名同一个类加载器仅加载一次;
  2. 问题2,默认类加载器是可以实现的,因为它的职责就是保证唯一性
  3. 问题3和问题1是一样的问题;
  4. 问题4,如何实现jsp的热加载?
    1. jsp文件最终都会转成class文件,虽然内容发生了修改,但是类名还是一样的,类加载器会直接取方法区中已经存在的类信息,修改后的jsp是不会重新加载的,所以应该如何实现呢?思路:卸载掉当前jsp文件的类加载器,也就是说每个jsp文件对应一个唯一的类加载器,当一个jsp文件修改了,就直接卸载这个jsp类加载,重新创建类加载器,重新加载jsp文件;

2.7.3 tomcat自定义类加载器详解

tomcat几个主要的类加载器:
  • CommonLoader:tomcat最基本的类加载器,加载路径中的class可以被tomcat容器本身以及各个Webapp程序访问;
  • CatalinaLoader:tomcat容器私有的类加载器,加载路径中的class对Webapp不可见;
  • sharedLoader:各个Webapp共享的类加载器,加载路径中的class对于所有Webapp可见,但是对于tomcat容器不可见;
  • WebappClassLoader:各个WebApp私有的类加载器,加载路径中的class仅对当前webapp可见;

从上图的委派关系可以看出,

  • CommonClassLoader能加载的类都可以被CatalinaClassLoader和SharedClassLoader使用,从而实现了公共类库的共用;
  • CatalinaClassLoader和SharedClassLoader自己能加载的类则与对方互相隔离;
  • WebappClassLoader可以使用SharedClassLoader加载到的类,但是各个WebappClassLoader实例之间互相隔离;
  • JasperLoader加载范围仅仅是这个JSP文件所编译出来的那一个class文件,当Web容器检测到JSP文件被修改了,会替换掉目前的JasperLoader实例,并通过重新加载一个新的Jsp类加载器来实现JSP文件的热加载功能;

tomcat这种类加载机制是否违背了java推荐的双亲委派模型?

  • 答案是违背了。因为tomcat为了实现隔离性,没有遵循双亲委派机制,每个WebappClassLoader加载自己目录下的class文件,不会向上委托给父类加载器,因此是打破了双亲委派机制;

2.7.4 tomcat打破双亲委派的简单实现

继承ClassLoader类,重写protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException方法,加载指定类时不再向上委托给父类加载器;

代码:

java">package com.ddu.jvm;import java.io.FileInputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Objects;/*** 自定义类加载器*/
public class DduClassLoaderHelper {public static void main(String[] args) {// d://uclazz/com/ddu/jvm/Order1.classDduClassLoader dduClassLoader2 = new DduClassLoader("D:/uclazz");try {Class<?> clazz2 = dduClassLoader2.loadClass("com.ddu.jvm.Order2");Object order2 = clazz2.newInstance();Method method1 = clazz2.getDeclaredMethod("print", null);method1.invoke(order2, null);System.out.println(clazz2.getClassLoader().getClass().getName());} catch (ClassNotFoundException e) {throw new RuntimeException(e);} catch (NoSuchMethodException e) {throw new RuntimeException(e);} catch (InvocationTargetException e) {throw new RuntimeException(e);} catch (IllegalAccessException e) {throw new RuntimeException(e);} catch (InstantiationException e) {throw new RuntimeException(e);}// d://uclazz/com/ddu/jvm/Order1.classDduClassLoader dduClassLoader = new DduClassLoader("D:/uclazz");try {Class<?> clazz = dduClassLoader.loadClass("com.ddu.jvm.Order1");Object order = clazz.newInstance();Method method = clazz.getDeclaredMethod("print", null);method.invoke(order, null);System.out.println(clazz.getClassLoader().getClass().getName());} catch (ClassNotFoundException e) {throw new RuntimeException(e);} catch (NoSuchMethodException e) {throw new RuntimeException(e);} catch (InvocationTargetException e) {throw new RuntimeException(e);} catch (IllegalAccessException e) {throw new RuntimeException(e);} catch (InstantiationException e) {throw new RuntimeException(e);}}static class DduClassLoader extends ClassLoader {private String classPath;public DduClassLoader(String classPath) {this.classPath = classPath;}private byte[] loadByte(String name) throws Exception {name = name.replaceAll("\\.", "/");FileInputStream fis = null;byte[] data = null;try {// d://uclazz/com/ddu/jvm/Order1.classfis = new FileInputStream(classPath + "/" + name + ".class");int length = fis.available();data = new byte[length];fis.read(data);} catch (Exception e) {throw new RuntimeException("加载class文件异常!");} finally {if (Objects.nonNull(fis)) {fis.close();}}return data;}/*** 如果只是为了自定义类加载器,直接重写当前方法就可以** @param name The <a href="#name">binary name</a> of the class* @return* @throws ClassNotFoundException*/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);}}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();// If still not found, then invoke findClass in order// to find the class.long t1 = System.nanoTime();// 自定义的类不再委托给父类加载if(name.startsWith("com.ddu.jvm")){c = findClass(name);// 非自定义的类还是按双亲委派机制加载}else {c = this.getParent().loadClass(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;}}}
}

运行结果:

java">2 d: uclazz order load path:D:\project\ddu-base\ddu-base
com.ddu.jvm.DduClassLoaderHelper$DduClassLoader
d: uclazz order load path:D:\project\ddu-base\ddu-base
com.ddu.jvm.DduClassLoaderHelper$DduClassLoader

Order源码:

两个类自己修改打印日志的内容然后重新编译就可以使用了

java">package com.ddu.jvm;import java.io.File;public class Order {public void print(){File file = new File("");System.out.println("project ddu order load path:"+file.getAbsoluteFile());}
}

2.7.5 tomcat的JasperLoader热加载简单实现 

原理:后台启动线程监听JSP文件变化,如果文件内容发生了变化,找到该jsp对应的servlet类的加载器引用(gcroot),重新生成新的JasperLoader加载器赋值给引用,然后加载新的jsp对应的servlet类,之前的那个加载器因为没有gcroot,下次gc的时候会被垃圾回收;

具体tomcat实现的方式参考:Tomcat热部署与热加载_smarttomcat开启热部署-CSDN博客


http://www.ppmy.cn/news/1442471.html

相关文章

Pow(x,n)——力扣

python&#xff08;快速幂&#xff09; 50. Pow(x, n) 已解答 中等 相关标签 相关企业 实现 pow(x, n) &#xff0c;即计算 x 的整数 n 次幂函数&#xff08;即&#xff0c;xn &#xff09;。 示例 1&#xff1a; 输入&#xff1a;x 2.00000, n 10 输出&#xff1a;10…

【深度学习】Dropout、DropPath

一、Dropout 1. 概念 Dropout 在训练阶段会让当前层每个神经元以drop_prob&#xff08; 0 ≤ drop_prob ≤ 1 0\leq\text{drop\_prob}\leq1 0≤drop_prob≤1&#xff09;的概率失活并停止工作&#xff0c;效果如下图。 在测试阶段不会进行Dropout。由于不同批次、不同样本的神…

Linux多进程(二)进程通信方式一 管道

管道的是进程间通信&#xff08;IPC - InterProcess Communication&#xff09;的一种方式&#xff0c;管道的本质其实就是内核中的一块内存(或者叫内核缓冲区)&#xff0c;这块缓冲区中的数据存储在一个环形队列中&#xff0c;因为管道在内核里边&#xff0c;因此我们不能直接…

牛客NC99 多叉树的直径【较难 深度优先 Java/Go/PHP】

题目 题目链接&#xff1a; https://www.nowcoder.com/practice/a77b4f3d84bf4a7891519ffee9376df3 思路 核心就是树的最大直径(globalMax)一定是以某一个node为root最长的两个path-to-leaf. 就是普通dfs的同时算路径长度。时间: O(n), DFS一次 空间: O(n)参考答案Java impo…

如何使用 Internet Download Manager (IDM) 来加速和优化你的下载体验 IDM 6.41下载神器

在当今信息爆炸的时代&#xff0c;下载文件和媒体内容已成为我们日常生活的一部分。无论是工作学习还是娱乐休闲&#xff0c;我们都需要从互联网上下载各种资源。为了提高下载效率和确保文件完整性&#xff0c;选择一款优秀的下载管理软件至关重要。Internet Download Manager …

[docker] volume 补充 环境变量 参数

[docker] volume 补充 & 环境变量 & 参数 这里补充一下 volume 剩下的内容&#xff0c;以及添加参数(ARG) 和 环境变量 ENV 的内容 read only volumes ❯ docker run-p 3000:80--rm--name feedback-app-v feedback:/app/feedback-v "$(pwd):/app"-v /app/…

SQLite导出数据库至sql文件

SQLite是一款实现了自包含、无服务器、零配置、事务性SQL数据库引擎的软件库。SQLite是世界上部署最广泛的SQL数据库引擎。 SQLite 是非常小的&#xff0c;是轻量级的&#xff0c;完全配置时小于 400KiB&#xff0c;省略可选功能配置时小于250KiB。 SQLite 源代码不受版权限制。…

Node.js使用

Node.js是一个基于Chrome V8引擎的JavaScript运行环境&#xff0c;它使得JavaScript能够脱离浏览器&#xff0c;直接在服务器端运行。Node.js的异步I/O模型使其在处理高并发请求时表现出色&#xff0c;适用于构建网络应用、实时应用等。以下是对Node.js使用的总结&#xff1a; …