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

一 类加载运行过程

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

源码:

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包里的类不是一次性全部加载的,是使用到时才会加载;

源码:

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;}}
}

运行结果:

load HelloWordHelper...
loader Order ......
initial Order ... 
load HelloWordHelper

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

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

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

类加载器示例:

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"));}
}

运行结果:

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部分源码:

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()实现:

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)实现源码
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方法

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方法实现双亲委派机制 

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再加载一次,保证被加载类的唯一性;
    • 被加载类唯一性:加载器类+目标类

示例:

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方法;

示例:

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

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());}
}

运行结果:

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方法,加载指定类时不再向上委托给父类加载器;

代码:

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;}}}
}

运行结果:

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源码:

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

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.mzph.cn/bicheng/1757.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

初识java——jdk?环境变量?及关于安装jdk的步骤

文章目录 JDK的安装在安装JDK时遇到的问题&#xff1a; 背景知识一 什么是jdkjdk简介jdk文件详解&#xff1a;1 bin目录&#xff1a;2 lib目录&#xff1a;3 include目录.exe文件是可执行的应用程序&#xff0c;这个我们都清楚&#xff0c;但.dll文件又是做什么的呢&#xff1f…

数据结构学习之路--玩转队列的内核知识(附C源码)

嗨嗨大家~我又来啦&#xff01;今天为大家带来的是与队列相关的知识。我们马上进入知识的海洋~ 目录 前言 一、队列 1 队列的概念 2 队列的实现 2.1 队列的定义 2.2 队列的初始化 2.3 队列的判空 2.4 入队 2.5 出队 2.6 取队头元素 2.7 取队尾元素 2.8 取…

37、Tomato(VulnHub)

Tomato 一、nmap 2211是ssh的端口&#xff0c;21的ftp也不是弱密码 二、web渗透 随便看看 目录爆破 /seclists/Discovery/Web-Content/common.txt /antibot_image/antibots/readme.txt 发现该站点存在反爬机制 /antibot_image/antibots/info.php 提示我们该网页存在个参数 GET&…

SFP、SFP+、SFP28 与 QSFP28 收发器之间的差异:兼容性和性能

近年来&#xff0c;网络技术发展迅速&#xff0c;因此&#xff0c;计算专业人员面临着越来越令人困惑的术语和缩写词。 管理数据中心时必须了解的一个关键领域是收发器&#xff0c;特别是 SFP (1550nm/1310nm)、SFP (850nm) 和 QSFP28 (4x25G) 之间的差异。 这些型号在兼容性方…

深入浅出 SQL 优化:全面提升查询性能的技巧

文章目录 前言一、表结构分析1. 索引分析2. 数据类型分析3. 思考反范式设计的适用场景与潜在风险3.1数据冗余3.2 数据一致性3.3 更新性能 4. 关注临时表的创建与使用。4.1.尽量减少临时表的使用&#xff0c;以降低系统资源的消耗。4.2 使用合适的索引和数据类型优化临时表的性能…

HarmonyOS ArkUI实战开发-窗口模块(Window)

窗口模块用于在同一物理屏幕上&#xff0c;提供多个应用界面显示、交互的机制。 对应用开发者而言&#xff0c;窗口模块提供了界面显示和交互能力。对于终端用户而言&#xff0c;窗口模块提供了控制应用界面的方式。对于操作系统而言&#xff0c;窗口模块提供了不同应用界面的…

swiper 去掉轮播图上的小点点 小圆圈(完美解决方案)

文章目录 问题描述解决方案问题复现处理方案 问题描述 大家好&#xff01;我是夏小花&#xff0c;今天是2024年4月22日|农历三月十四&#xff0c;今天这篇博文主要解决swiper 去掉轮播图上的小点点 小圆圈&#xff0c;具体解决方案如下所示 解决方案 问题复现 现在现在可以看…

数据结构初阶-二叉树

&#x1f308;个人主页&#xff1a;羽晨同学 &#x1f4ab;个人格言:“成为自己未来的主人~” 二叉树 树概念和结构 树的概念 树是一种非线性的数据结构&#xff0c;它是由n&#xff08;n>0&#xff09;个有限节点组成的一个具有层次关系的集合&#xff0c;把它叫做树…

python--pyQt5 进度条:QProgressBar

https://www.cnblogs.com/itwangqiang/articles/14959401.html https://blog.csdn.net/weixin_43990846/article/details/123880081 进度条用于向用户指示操作的进度&#xff0c;并向他们保证应用程序仍在运行 例 1 import sys from PyQt5.QtWidgets import QApplication, QWi…

【机器学习】特征筛选:提升模型性能的关键步骤

一、引言 在机器学习领域&#xff0c;特征筛选是一个至关重要的预处理步骤。随着数据集的日益庞大和复杂&#xff0c;特征的数量往往也随之激增。然而&#xff0c;并非所有的特征都对模型的性能提升有所贡献&#xff0c;有些特征甚至可能是冗余的、噪声较大的或者与目标变量无关…

Webpack-

定义 静态模块&#xff1a;指的是编写代码过程中的html&#xff0c;css&#xff0c;js&#xff0c;图片等固定内容的文件 打包&#xff1a;把静态模块内容压缩、整合、翻译等&#xff08;前端工程化&#xff09; 1&#xff09;把less/sass转成css代码 2&#xff09;把ES6降级…

OpenHarmony鸿蒙南向开发案例:【智能加湿器】

样例简介 智能加湿器具有实时监控其所处环境温度、湿度&#xff0c;并通过数字管家设置日程&#xff0c;自动打开加湿器控制湿度功能。显示界面使用DevEco Studio 编写的js应用&#xff0c;具有很好的兼容和移植特性。硬件上采用了带有HDF框架的驱动模型&#xff0c;通过GPIO和…

nodejs在控制台打印艺术字

const figlet require("figlet");figlet("SUCCESS", function (err, data) {if (err) {console.log("Something went wrong...");console.dir(err);return;}console.log(data);}); 参考链接&#xff1a; https://www.npmjs.com/package/figlet…

Android studio配置Flutter(看这一篇就够了)

Flutter 是 Google 推出并开源的移动应用开发框架&#xff0c;主打跨平台、高保真、高性能。开发者可以通过 Dart 语言开发 App&#xff0c;一套代码同时运行在 iOS 和 Android平台。 Flutter 提供了丰富的组件、接口&#xff0c;开发者可以很快地为 Flutter 添加 Native&#…

基于开源CrashRpt与微软开源Detours技术深度改造的异常捕获库分享

目录 1、异常捕获模块概述 2、为什么需要异常捕获模块&#xff1f; 3、在有些异常的场景下是没有生成dump文件的 4、开源异常捕获库CrashRpt介绍 5、对开源库CrashRpt的改进 C软件异常排查从入门到精通系列教程&#xff08;专栏文章列表&#xff0c;欢迎订阅&#xff0c;持…

【图说】VMware Ubuntu22.04 详细安装教程

前言 无论是从事 Linux 开发工作&#xff0c;还是希望电脑运行双系统&#xff0c;VMware 虚拟机都是我们日常工作不可或缺的工具。本章将会重点介绍 VMware 安装流程&#xff0c;以及在 VMware 上如何运行、使用 Ubuntu22.04 系统。 一、VMware 下载安装 1.1 VMware 官网下载…

TensorFlow文件读取 --TFRecords文件

TFRecords文件 是一种二进制文件&#xff0c;能够很好的利用内存&#xff0c;更方便复制和移动&#xff0c;并且不需要单独的标签文件 使用步骤 1&#xff09;获取数据 2&#xff09;将数据填入到Example协议内存块&#xff08;protocol buffer&#xff09; 3&#xff09;将协…

001-谷粒商城-微服务剖析

1、架构图 还是很强的&#xff0c;该有的都有 2、微服务模块 SpringCloudAlibaba组件包括 SentinelNacosRocketMQSeata 搭配SpringCloudAlibaba组件 OpenFeignGateWayRibbn gateway使用了SpringWebFlux&#xff0c;前几天研究到&#xff0c;为什么springboot不直接使用Spri…

阿里云mysql8.0 this is incompatible withsql mode=only full group by

阿里云RDS中mysql5.6升级为8.0后&#xff0c;出现如下问题&#xff1a; ### Error querying database. Cause:java.sql.SQLSyntaxErrorException: Expression #1 of SELECT listis not in GROUP BY clause and contains nonaggregatedcolumn temp.product_id which is not fun…

2024抖店新政策!抖音小店的发展趋势!新手必看!

哈喽~我是电商月月 准备开抖店的新手朋友注意了&#xff0c;最近抖音严查无货源违规商家&#xff0c;还发布了取消新手期的政策&#xff0c;这说明了两点 1. 生态环境正在改变 无规矩不成方圆&#xff0c;违规的都是故意放错类目以及&#xff0c;靠S单非法获得销量&#xff…