JVM类加载机制详解(JDK源码级别)

提示:从JDK源码级别彻底剖析JVM类加载机制、双亲委派机制、全盘负责委托机制、打破双亲委派机制的程序、Tomcat打破双亲委派机制、tomcat自定义类加载器详解、tomcat的几个主要类加载器、手写tomcat类加载器

文章目录

  • 前言
  • 一、loadClass的类加载大概有如下步骤
  • 二、java中类加载器的分类
  • 三、双亲委派机制
    • 3.1、为什么要设计双亲委派机制
    • 3.2、什么是全盘负责委托机制。
    • 3.3、打破双亲委派机制的程序:
  • 4、Tomcat打破双亲委派机制
    • 4.1、tomcat解决什么问题?
    • 4.2、tomcat如果使用双亲委派机制行不行?
    • 4.3、tomcat自定义类加载器详解
    • 4.4、自定义类加载器示例:
    • 4.5、tomcat的实现
  • 总结


前言

例如:随着人工智能的不断发展,机器学习这门技术也越来越重要,很多人都开启了学习机器学习,本文就介绍了机器学习的基础内容。


一、loadClass的类加载大概有如下步骤

加载 -》验证 -》准备 -》解析 -》初始化 -》使用 -》卸载。

  • 加载:在硬盘中查找并通过io读入字节码文件。使用到类时才会加载。例如调用类的main方法,new对象等,在加载阶段会在内存中生成一个点这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口
  • 验证:校验字节码文件的正确性。正确的字节码文件应该是以 cafe babe 开头的,并且是0034 这样的,不能说随便一个格式的字节码都能正确识别
  • 准备:是会将变量,先赋一个默认值,如果是boolean就是false,如果是integer就是0.比如你定义的 int a = 10。那么准备这部,就是int a=0
  • 解析:将符号引用转变为直接引用,其实就是转换为内存地址。比如说public static void main(String [] args)。这些符号,肯定要存到内存中的,而且是存的内存地址。就是把这些符号转换为地址的过程,这个步骤有个名词叫做静态链接。动态链接:是指在程序运行期间完成的将符号引用替换为直接引用。(在加载的时候还不执行,在运行的时候,才执行的。)
  • 初始化:对类的静态变量初始化为指定的值,执行静态代码块。(一些静态代码块,也是这个阶段去执行。可以参考一下类的加载顺序:子类的静态代码块-》父类的静态代码块-》父类的构造方法-》子类的构造方法)

二、java中类加载器的分类

  • 引导类加载器(根加载器BootStarpClassLoader):负责加载支撑JVM运行的位于JRE的lib目录下的核心类库,比如rt.jar、charsets.jar等
  • 扩展类加载器(扩展类加载器ExtClassLoader):负责加载支撑JVM运行的位于JRE的lib目录下的ex扩展目录的jar包
  • 应用程度类加载器(系统类加载器AppClassLoader):负责加载ClassPath路径下的类包,主要就是加载你自己写的那些类
  • 自定义类加载器(继承了java.lang.ClassLoader的类加载器):负责加载用户自定义路径下的类包(项目的classPath下找)

引导类加载器,不用管,是c语言实现的。c语言在实现引导类加载器的时候,会通过一个Launcher类,初始化这个Launcher类的时候把扩展类加载器、应用程序类加载器给构造出来,并且把他俩的关系给搞出来。扩展类加载器是应用程序类加载器的父类。
通过Java命令执行代码的大体流程如下
在这里插入图片描述
类加载器的初始化过程:
Launcher类是类加载的关键类。而extClassLoader、appClassLoader、的父类是ClassLoader.java类。

参见类运行加载全过程图可知其中会创建JVM启动器实例sun.misc.Launcher。
sun.misc.Launcher初始化使用了单例模式设计,保证一个JVM虚拟机内只有一个
sun.misc.Launcher实例。
在Launcher构造方法内部,其创建了两个类加载器,分别是
sun.misc.Launcher.ExtClassLoader(扩展类加载器)和sun.misc.Launcher.AppClassLoader(应
用类加载器)。
JVM默认使用Launcher的getClassLoader()方法返回的类加载器AppClassLoader的实例加载我们
的应用程序。

1 //Launcher的构造方法
2 public Launcher() {3 Launcher.ExtClassLoader var1;
4 try {
5 //构造扩展类加载器,在构造的过程中将其父加载器设置为null
6 var1 = Launcher.ExtClassLoader.getExtClassLoader();
7 } catch (IOException var10) {
8 throw new InternalError("Could not create extension class loader", var10);
9 }
10
11 try {
12 //构造应用类加载器,在构造的过程中将其父加载器设置为ExtClassLoader,
13 //Launcher的loader属性值是AppClassLoader,我们一般都是用这个类加载器来加载我们自
己写的应用程序
14 this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
15 } catch (IOException var9) {
16 throw new InternalError("Could not create application class loader", var9);
17 }
18
19 Thread.currentThread().setContextClassLoader(this.loader);
20 String var2 = System.getProperty("java.security.manager");
21 。。。 。。。 //省略一些不需关注代码
22
23 }

三、双亲委派机制

在这里插入图片描述
双亲委派机制:双亲委派机制,简单的说,就是先由父类加载,父类没有了再由儿子加载。举个例子,比如你现在要加载Math这个类,那么就会

  1. 由应用程序类加载器(AppClassLoader)去扫描应用程序类加载器已经加载的类,如果扫描到了,直接返回。(刚进来肯定是没有,直接委派给上级)
  2. 如果应用程序类加载器(AppClassLoader)没有扫描到,他就会委托他的父加载器扩展类加载器(ExtClassLoader)去加载,扩展加载器也是一样,先扫描扩展加载器已经加载的类(jdk中的ex包下的类),如果扫描到了,直接返回;
  3. 如果扩展类加载器(ExtClassLoader)没扫描到,就委托扩展加载器的父加载器引导类加载器(BootStarpClassLoader)去加载。他也是一样,先扫描自己已经加载的类,如果扫描到了(扫描jdk中的lib包的类),直接返回;如果没扫描到,就会委托他的子加载器,扩展类加载器去加载。
  4. 如果扩展类加载器(ExtClassLoader)扫描到他已经加载的类,扫描到了,直接返回,如果没扫描到,就会给他的子加载器(应用程序类加载器)去扫描。
  5. 应用程序类加载器去扫描它应该扫描的路径,也就是项目的classPath下的包,扫描到了,直接返回。(没扫描到,报classNotFind?)
    简单总结一下,双亲委派机制,就是说,先由父类去加载,父类加载不到,再由子类去加载,子类最后也加载不到,就会提示 class not
    find

思考一下,为啥会是双亲委派呢,每次都要先依次到最顶层,然后最顶层没有还有依次往下到最下层,这样性能不是会变低吗。第一次加载的时候可能确实性能会低,但是一旦加载完毕了,就直接存在应用程序类加载器了(这个里面有一个list是存的已经加载好的类),直接从里面读就行,不用在依次到最上层的引导类加载器去获取类对象了。

我们常说的应用程序类加载器的父加载器是扩展类加载器,并不是说,应用程序类加载器继承了扩展类加载器,而是说,应用程序类加载器的其中一个属性(父加载器)的值 是扩展类加载器。

3.1、为什么要设计双亲委派机制

  1. 沙箱安全机制:防止核心API类库被随意篡改。比如你自己写了一个java.lang.String.class类不会被加载(因为是先父类加载,加载到了直接返回)
  2. 避免类的重复加载:当父类已经加载了该类时,就没有必要子类在加载一遍了,保证了被加载类的唯一性。 比如你手写了一个String类,包路径也叫做:java.lang.String。那么一上来应用程序类(最下级)加载器肯定是没有,然后他委托给扩展类加载器,然后扩展类加载器委托给引导类加载器,然后引导类加载器直接扫描到,就直接返回了,就不往下走了,所以你自己手写的Stting类没起到作用,直接用的还是java的String类。

3.2、什么是全盘负责委托机制。

全盘负责委托机制:指当一个ClassLoader装载一个类时,除非显示的使用另外一个classLoader,否则该类所依赖及引用的类也由这个ClassLoader载入。(比如说是appClassLoader加载的UserA类,然后UserA类中有一个静态属性是UserB类,因为是静态的嘛,A加载的时候,B也得被加载。那么appClassLoader在创建userA对象的时候,就直接把userA、userB同时创建了)

3.3、打破双亲委派机制的程序:

tomcat。假设我tomcat要同时部署2个war包,一个是A、一个是B。其中A是依赖的spring4,B是依赖是spring5。那么两个spring中肯定有很多包名是全限定路径都一样,那肯定不能是双亲委派机制,走第一个了第二个就不走了,那肯定是不行的。所以tomcat一定是打破了双亲委派机制的。

4、Tomcat打破双亲委派机制

4.1、tomcat解决什么问题?

以tomcat类加载为例,我们思考一下,tomcat作为一个web容器,他要解决什么问题

  1. 一个web容器可能要部署多个不同的应用程序(服务),不同的应用程序可能会依赖同一个第三方类库的不同版本(比如A服务是依赖的spring3,b服务是依赖的spring4),因此要保证每个服务的类库都是独立的,保证相互隔离。
  2. 部署在同一个web容器中相同的类库的相同版本可以共享。否则如果容器内有10个服务,那么就要有10份相同的类库加载进虚拟机。(如果a、b、c等10个服务都依赖了spring2.3,难道要把spring2.3内的类加载10遍吗?)
  3. web容器也有自己依赖的类库,不能与应用程序的类库混淆。基于安全考虑,应该让容器中的类库和程序的类库隔离开来。
  4. web容器要支持jsp的修改,我们知道jsp文件最终也是要编译成clas文件才能在虚拟机中运行,但程序运行后修改jsp已经是司空见惯的事,web容器需要支持jsp修改后不用重启。

4.2、tomcat如果使用双亲委派机制行不行?

不行的,为什么?
5. 1.第一个问题:如果使用默认的类加载机制,那么是无法加载两个类库的不同版本的,默认的双亲委派机制是不管你哪个版本,只关心你的全限定路径一样不一样
6. 2.第二个问题,默认的类加载器是能够实现的,因为他的职责就是保证唯一性。
7. 3.和第一个问题一样

4.我们想一下,要怎么实现tomcat的热加载。jsp其实也是class文件,那么如果修改了,但类名还一样,类加载器会直接取方法区中已存在的,修改后的jsp是不会被重新加载的。那么怎么办呢?我们可以直接卸载掉这个jsp文件的类加载器,所以呢,就是给每个jsp文件对应一个唯一的类加载器,当一个jsp文件修改了,就直接卸载这个jsp类加载器。重新创建类加载器,重写加载jsp文件。(搞个线程去监听这个jsp文件夹的变动,有变动了就重新加载)

4.3、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版本。

大概是,每个war包都加载自己war内的类,然后tomcat有几个war包就有几个war的加载器(webAppClassLoader)。然后其他类,还是会委托给对应的上级类加载器。

从图中的委派关系中可以看出:

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

4.4、自定义类加载器示例:

自定义类加载器只需要继承 java.lang.ClassLoader 类,该类由俩个核心方法,一个是loadClass(String,boolean),实现了双亲委派机制,还有一个是findClass,默认实现是空方法,所以我们自定义类加载器主要是重写findClass方法。
大致步骤:
1、继承 ClassLoader 类
2、重写 findClass 方法。
3、重写loadClass方法(想跨过双亲委派机制就重写,不想跨过就不需要写)
全部代码如下:
其中不重写 loadClass 方法的时候,是实现自定义类加载器去加载类的方法
其中重写了 loadClass 方法的时候,是实现了跨过双亲委派机制的去用自定义类加载器去加载的实现。

package com.zheng.config;

import java.io.FileInputStream;
import java.lang.reflect.Method;

/**
 * @author: ztl
 * @date: 2023/11/18 22:29
 * @desc: 自己的自定义类加载器
 *  自定义类加载器只需要继承 java.lang.ClassLoader 类,这个类由俩个核心方法:
 *      loadClass(String,boolean)   实现了双亲委派机制
 *      findClass       实现了空方法,(所以我们自定义类加载器主要是重写findClass方法)
 */
public class MyClassLoaderTest {

    static public class MyClassLoader extends ClassLoader {

        private String classPath;

        public MyClassLoader(String classPath){
            this.classPath = classPath;
        }

        /**
         * 这个是读取class包用的
         * @param name
         * @return
         * @throws Exception
         */
        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;
        }

        /**
         *
         * @param name
         * @return 这个是自定义加载器要用的
         * @date: 2023/11/19 17:38
         * content:
         *  name就是类的全限定路径
         *  defineClass 是ClassLoader中的方法,
         */
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            try {
                byte[] data = loadByte(name);
                //name 类名。 data 类的二进制数组文件
                return defineClass(name, data, 0, data.length);
            } catch (Exception e) {
                e.printStackTrace();
                throw new ClassNotFoundException();
            }
        }

        /**
         * 这个是打破双亲委派机制用的
         * 正常的loadClass是双亲委派式的读取class文件。
         * 但是你现在不是想打破双亲委派嘛,那就重写java的双亲委派,走自己的加载程序,就可以打破了
         *
         */
        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();
                    // 这几行是双亲委派的代码,注掉他,直接让他 c==null,走自己的findClass
//                    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
//                    }

                    // 但是由于User1,继承了Object类,所以初始化user的时候,就会初始Object,
                    // 由于Object是由BooStrapClassLoader加载的,你没法直接不走双亲委派,所以呢,加个判断,
                    // 如果是我们自己的类,则走自己的类加载器去加载,如果是Object之类的类,就走原来的双亲委派机制
                    if (!name.startsWith("com.zheng.po")){
                        // 走jdk的双亲委派机制
                        c = this.getParent().loadClass(name);
                    }else {
                        // 走自己重写的findClass类
                        c = findClass(name);
                    }

                    if (c == null) {
                        // If still not found, then invoke findClass in order
                        // to find the class.
                        long t1 = System.nanoTime();
                        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;
            }
        }

    }

    /**
     *
     * @content 测试,自定义类加载器
     * @return
     * @date: 2023/11/19 18:07
     */
    public static void main(String args[]) throws Exception {
        //初始化自定义类加载器,会先初始化父类ClassLoader,其中会把自定义类加载器的父加载器设置为应用程序类加载器AppClassLoader
        // 自定义类加载器的,加载路径。
        MyClassLoader classLoader = new MyClassLoader("D:/test");
        //D盘创建 com/zheng/po 几级目录,将User类的复制类User1.class丢入该目录
        // 1、这个 com/zheng/po 就是你user1的包路径, 2、这个必须是.class文件,不能是 .java文件
        Class clazz = classLoader.loadClass("com.zheng.po.User1");
        Object obj = clazz.newInstance();
        Method method = clazz.getDeclaredMethod("sout", null);
        method.invoke(obj, null);

        // 父加载器有,就用父加载器的,然后直接返回;父加载器没有,就用子加载器的结果
        // 如果你的appClassLoader就有class1的话,就会打印出来 sun.misc.Launcher$AppClassLoader
        // 如果你的AppClassLoader里面没有的话,就会走自己的 MyClassLoader。com.zheng.config.MyClassLoaderTest$MyClassLoader
        // 因为你的项目内现在有一个 com.zheng.po.user1.class
        // 然后d盘下面还有一个 com.zheng.po.user1.class
        // 所以,他们的类名一模一样,找到一个,就直接返回了。就跟你自己手写一个String类,和java的String类一样,用上级的类加载器的String类

        // 如果已经重写了 loadClass ,那么就是打破双亲委派机制的 com.zheng.config.MyClassLoaderTest$MyClassLoader
        System.out.println(clazz.getClassLoader().getClass().getName());
    }


}

4.5、tomcat的实现

tomcat的实现(不同的war包中,去加载不同的类。这个类的全限定路径一样,名字一样的话。):
// tomcat就是这么实现的:用同一个类(MyClassLoader)的两个实现(classLoader1、classLoader2),去加载不同的类
(跟上面的一样,就是main方法改了改。)
代码块1:

public static void main(String args[]) throws Exception {
    //初始化自定义类加载器,会先初始化父类ClassLoader,其中会把自定义类加载器的父加载器设置为应用程序类加载器AppClassLoader
    // 自定义类加载器的,加载路径。
    MyClassLoader classLoader = new MyClassLoader("D:/test");
    //D盘创建 com/zheng/po 几级目录,将User类的复制类User1.class丢入该目录
    // 1、这个 com/zheng/po 就是你user1的包路径, 2、这个必须是.class文件,不能是 .java文件
    Class clazz = classLoader.loadClass("com.zheng.po.User1");
    Object obj = clazz.newInstance();
    Method method = clazz.getDeclaredMethod("sout", null);
    method.invoke(obj, null);

    // 父加载器有,就用父加载器的,然后直接返回;父加载器没有,就用子加载器的结果
    // 如果你的appClassLoader就有class1的话,就会打印出来 sun.misc.Launcher$AppClassLoader
    // 如果你的AppClassLoader里面没有的话,就会走自己的 MyClassLoader。com.zheng.config.MyClassLoaderTest$MyClassLoader
    // 因为你的项目内现在有一个 com.zheng.po.user1.class
    // 然后d盘下面还有一个 com.zheng.po.user1.class
    // 所以,他们的类名一模一样,找到一个,就直接返回了。就跟你自己手写一个String类,和java的String类一样,用上级的类加载器的String类

    // 如果已经重写了 loadClass ,那么就是打破双亲委派机制的 com.zheng.config.MyClassLoaderTest$MyClassLoader
    System.out.println(clazz.getClassLoader().getClass().getName());


    System.out.println("=================================================");

    // tomcat就是这么实现的:用同一个类(MyClassLoader)的两个实现(classLoader1、classLoader2),去加载不同的类
    MyClassLoader classLoader1 = new MyClassLoader("D:/test1");
    Class clazz1 = classLoader1.loadClass("com.zheng.po.User2");
    Object obj1 = clazz1.newInstance();
    Method method1 = clazz1.getDeclaredMethod("sout", null);
    method1.invoke(obj1, null);
    System.out.println(clazz1.getClassLoader().getClass().getName());
}

代码块2:

package com.zheng.config;

import java.io.FileInputStream;
import java.lang.reflect.Method;

/**
 * @author: ztl
 * @date: 2023/11/18 22:29
 * @desc: 自己的自定义类加载器
 *  自定义类加载器只需要继承 java.lang.ClassLoader 类,这个类由俩个核心方法:
 *      loadClass(String,boolean)   实现了双亲委派机制
 *      findClass       实现了空方法,(所以我们自定义类加载器主要是重写findClass方法)
 */
public class MyClassLoaderTest {

    static public class MyClassLoader extends ClassLoader {

        private String classPath;

        public MyClassLoader(String classPath){
            this.classPath = classPath;
        }

        /**
         * 这个是读取class包用的
         * @param name
         * @return
         * @throws Exception
         */
        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;
        }

        /**
         *
         * @param name
         * @return 这个是自定义加载器要用的
         * @date: 2023/11/19 17:38
         * content:
         *  name就是类的全限定路径
         *  defineClass 是ClassLoader中的方法,
         */
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            try {
                byte[] data = loadByte(name);
                //name 类名。 data 类的二进制数组文件
                return defineClass(name, data, 0, data.length);
            } catch (Exception e) {
                e.printStackTrace();
                throw new ClassNotFoundException();
            }
        }

        /**
         * 这个是打破双亲委派机制用的
         * 正常的loadClass是双亲委派式的读取class文件。
         * 但是你现在不是想打破双亲委派嘛,那就重写java的双亲委派,走自己的加载程序,就可以打破了
         *
         */
        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();
                    // 这几行是双亲委派的代码,注掉他,直接让他 c==null,走自己的findClass
//                    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
//                    }

                    // 但是由于User1,继承了Object类,所以初始化user的时候,就会初始Object,
                    // 由于Object是由BooStrapClassLoader加载的,你没法直接不走双亲委派,所以呢,加个判断,
                    // 如果是我们自己的类,则走自己的类加载器去加载,如果是Object之类的类,就走原来的双亲委派机制
                    if (!name.startsWith("com.zheng.po")){
                        // 走jdk的双亲委派机制
                        c = this.getParent().loadClass(name);
                    }else {
                        // 走自己重写的findClass类
                        c = findClass(name);
                    }

                    if (c == null) {
                        // If still not found, then invoke findClass in order
                        // to find the class.
                        long t1 = System.nanoTime();
                        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;
            }
        }

    }

    /**
     *
     * @content 测试,自定义类加载器
     * @return
     * @date: 2023/11/19 18:07
     */
    public static void main(String args[]) throws Exception {
        //初始化自定义类加载器,会先初始化父类ClassLoader,其中会把自定义类加载器的父加载器设置为应用程序类加载器AppClassLoader
        // 自定义类加载器的,加载路径。
        MyClassLoader classLoader = new MyClassLoader("D:/test");
        //D盘创建 com/zheng/po 几级目录,将User类的复制类User1.class丢入该目录
        // 1、这个 com/zheng/po 就是你user1的包路径, 2、这个必须是.class文件,不能是 .java文件
        Class clazz = classLoader.loadClass("com.zheng.po.User1");
        Object obj = clazz.newInstance();
        Method method = clazz.getDeclaredMethod("sout", null);
        method.invoke(obj, null);

        // 父加载器有,就用父加载器的,然后直接返回;父加载器没有,就用子加载器的结果
        // 如果你的appClassLoader就有class1的话,就会打印出来 sun.misc.Launcher$AppClassLoader
        // 如果你的AppClassLoader里面没有的话,就会走自己的 MyClassLoader。com.zheng.config.MyClassLoaderTest$MyClassLoader
        // 因为你的项目内现在有一个 com.zheng.po.user1.class
        // 然后d盘下面还有一个 com.zheng.po.user1.class
        // 所以,他们的类名一模一样,找到一个,就直接返回了。就跟你自己手写一个String类,和java的String类一样,用上级的类加载器的String类

        // 如果已经重写了 loadClass ,那么就是打破双亲委派机制的 com.zheng.config.MyClassLoaderTest$MyClassLoader
        System.out.println(clazz.getClassLoader().getClass().getName());
    }


}


总结

我们面试中常说的jvm调优,tomcat调优,都需要我们了解底层原理,才能更好的去优化,因此,了解这些组件的底层原理,还是挺有必要的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/692764.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Spring Boot通过自定义注解和Redis+Lua脚本实现接口限流

&#x1f604; 19年之后由于某些原因断更了三年&#xff0c;23年重新扬帆起航&#xff0c;推出更多优质博文&#xff0c;希望大家多多支持&#xff5e; &#x1f337; 古之立大事者&#xff0c;不惟有超世之才&#xff0c;亦必有坚忍不拔之志 &#x1f390; 个人CSND主页——Mi…

【CS.AI】AI引领编程新时代:深度探索GitHub Copilot

文章目录 引言0. TOP TAKEAWAYS 重要要点1. Copilot的基本功能2. 技术原理3. 优势与局限优势局限 4. 使用体验4.1 初次使用4.2 在 JetBrains 全家桶中使用 GitHub Copilot1. 安装插件2. 配置插件3. 使用 GitHub Copilot 4.3 日常开发4.4 体验与反馈 5. 对开发者生态系统的影响5…

深度学习500问——Chapter10:迁移学习(1)

文章目录 11.1 迁移学习基础知识 11.1.1 什么是迁移学习 11.1.2 为什么需要迁移学习 11.1.3 迁移学习的基本问题有哪些 11.1.4 迁移学习有哪些常用概念 11.1.5 迁移学习与传统机器学习有什么区别 11.1.6 迁移学习的核心及度量准则 11.1.7 迁移学习与其他概念的区别 11.1.8 什么…

【设计模式】行为型设计模式之 状态模式,带你探究有限状态机FSM的三种实现方式

什么是有限状态机 Finite state Machine FSM 简称状态机&#xff1a;状态机由三部分组成&#xff0c;状态(State) 事件(Event) 和动作(Action)组成。 其中事件也被称为转移条件&#xff0c;事件触发状态的转移和动作的执行。不过动作不是必须的&#xff0c;也可能只存在状态转…

人工智能与能源约束的矛盾能否化解

以下文章来源&#xff1a;澎湃新闻 人工智能技术在台前展示的是比特世界的算力、算法和数据&#xff0c;但其“轻盈的灵魂”背后则是土地、能源和水等物理世界“沉重的肉身”。根据本文三种情境的模拟测算&#xff0c;未来人工智能发展需要可持续的巨量能源支撑&#xff0c;能源…

DS:树与二叉树的相关概念

欢迎来到Harper.Lee的学习世界&#xff01;博主主页传送门&#xff1a;Harper.Lee的博客主页想要一起进步的uu可以来后台找我哦&#xff01; 一、树的概念及其结构 1.1 树的概念亲缘关系 树是一种非线性的数据结构&#xff0c;它是由n&#xff08;n>0&#xff09;个有限节点…

社区物资交易互助平台的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;管理员管理&#xff0c;基础数据管理&#xff0c;论坛管理&#xff0c;公告信息管理 前台账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;论坛&#xff0c;求助留言板&#xff0c;公…

OpenCV 双目相机标定

文章目录 一、简介1.1单目相机标定1.2双目相机标定二、实现代码三、实现效果参考资料一、简介 1.1单目相机标定 与单目相机标定类似,双目标定的目的也是要找到从世界坐标转换为图像坐标所用到的投影P矩阵各个系数(即相机的内参与外参)。具体过程如下所述: 1、首先我们需要…

王学岗鸿蒙开发(北向)——————(四、五、六)ArkUi声明式组件

普通组件 1,注意&#xff0c;如上图&#xff0c;build只能有一个根节点 2,Entry表示程序的入口 Component表示自定义的组件 Preview表示可以预览 3&#xff0c;图片存放的地方 4&#xff0c; Image组件最好只给宽度&#xff0c;给了高度又给宽度容易失真。 build() {Row() {/…

论文降痕指南:如何有效降低AIGC率

随着 AI 技术迅猛发展&#xff0c;各种AI辅助论文写作的工具层出不穷&#xff01; 为了防止有人利用AI工具进行论文代写&#xff0c;在最新的学位法中已经明确规定“已经获得学位者&#xff0c;在获得该学位过程中如有人工智能代写等学术不端行为&#xff0c;经学位评定委员会…

使用 ESP32 和 PlatformIO (arduino框架)实现 Over-the-Air(OTA)固件更新

使用 ESP32 和 PlatformIO 实现 Over-the-Air&#xff08;OTA&#xff09;固件更新 摘要&#xff1a; 本文将介绍如何在 ESP32 上使用 PlatformIO 环境实现 OTA&#xff08;Over-the-Air&#xff09;固件更新。OTA 更新使得在设备部署在远程位置时&#xff0c;无需物理接触设…

Python 基于阿里云的OSS对象存储服务实现本地文件上云框架

Python 基于阿里云的OSS对象存储服务实现将文件上云框架 文章目录 Python 基于阿里云的OSS对象存储服务实现将文件上云框架一、前言二、阿里云配置1、获取用户AKEY和AKeySecret2、创建Bucket 三、Python 阿里云oss上云框架1、安装oss2依赖库2、阿里云oss python 一、前言 未来…

Mysql 中的case-when

什么是 case-when case-when 是一种 sql 语句中的语法结构,结构如下&#xff1a; case 字段名 when 值 then 字段名|值 ... else 字段名|值 end case when 主要用于数据的 行列转换&#xff08;把一列数据转换为多列&#xff09; 前置条件&#xff1a; -- 表…

基于统一二维电子气密度表达式的通用MIS-HEMT紧凑模型

来源&#xff1a;A Compact Model for Generic MIS-HEMTs Based on the Unified 2DEG Density Expression&#xff08;TED 14年&#xff09; 摘要 本文提出了一种针对二维电子气&#xff08;ns&#xff09;密度和费米能级&#xff08;E_f&#xff09;的解析表达式&#xff0c…

【Pytorch】计算机视觉项目——卷积神经网络TinyVGG模型图像分类(如何使用自定义数据集)

目录 一、前言二、工作流程回顾三、详细步骤流程1. 环境配置2. 数据准备数据集下载数据存储结构&路径查看图片 3. 数据转换4. 自定义数据集&#xff08;Custom Dataset &#xff09;4.1 方法一&#xff1a;使用ImageFolder加载数据集信息查看张量转图片创建DataLoader 4.2 …

使用Python创建Word文档

使用Python创建Word文档 安装python-docx库创建Word文档代码效果 在这篇文章中&#xff0c;我们将介绍如何使用 Python创建一个Word文档。首先&#xff0c;我们需要安装python-docx库&#xff0c;然后通过一段简单的代码示例展示如何创建和编辑Word文档。 安装python-docx库 …

【Linux】进程(9):进程控制1

大家好&#xff0c;我是苏貝&#xff0c;本篇博客带大家了解Linux进程&#xff08;9&#xff09;进程控制1&#xff0c;如果你觉得我写的还不错的话&#xff0c;可以给我一个赞&#x1f44d;吗&#xff0c;感谢❤️ 目录 1 fork函数2 进程终止&#xff08;A&#xff09;终止是…

设计模式-策略模式(行为型)

行为型-策略模式 了解策略模式 策略模式是一种行为型设计模式&#xff0c;在策略模式中定义了一系列算法或者策略&#xff0c;并将这些策略封装到独立的类中&#xff0c;使得可以相互替换。在使用时&#xff0c;可以指定响应的策略使用。 角色 策略接口&#xff1a;对于某种…

免费使用GPT-4生成图片的方法

写在前言 hello&#xff0c;大家好&#xff0c;我是一点&#xff0c;喜欢编程&#xff0c;目前持续在关注AI领域的发展&#xff0c;希望可以结交一些有意思的朋友。 大家可以关注我的公号&#xff1a;【一点sir】&#xff0c;了解更多文章&#xff0c;希望可以和你们成为朋友…

10 设备树

掌握设备树是 Linux 驱动开发人员必备的技能! 1、什么是设备树 新版本 Linux 中,ARM 相关的驱动全部采用了设备树。Linux-4.1.15 支持设备树。我们了解一下设备树的起源、重点学习一下设备树语法。 设备树:Device Tree,就是“设备”和“树”,描述设备树的文件叫做 DTS(…