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

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

c 复制代码
参见类运行加载全过程图可知其中会创建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如果使用双亲委派机制行不行?

不行的,为什么?

  1. 1.第一个问题:如果使用默认的类加载机制,那么是无法加载两个类库的不同版本的,默认的双亲委派机制是不管你哪个版本,只关心你的全限定路径一样不一样

  2. 2.第二个问题,默认的类加载器是能够实现的,因为他的职责就是保证唯一性。

  3. 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 方法的时候,是实现了跨过双亲委派机制的去用自定义类加载器去加载的实现。

c 复制代码
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:

c 复制代码
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:

c 复制代码
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调优,都需要我们了解底层原理,才能更好的去优化,因此,了解这些组件的底层原理,还是挺有必要的。

相关推荐
Coder码匠1 小时前
Dockerfile 优化实践:从 400MB 到 80MB
java·spring boot
李慕婉学姐8 小时前
【开题答辩过程】以《基于JAVA的校园即时配送系统的设计与实现》为例,不知道这个选题怎么做的,不知道这个选题怎么开题答辩的可以进来看看
java·开发语言·数据库
奋进的芋圆10 小时前
Java 延时任务实现方案详解(适用于 Spring Boot 3)
java·spring boot·redis·rabbitmq
sxlishaobin10 小时前
设计模式之桥接模式
java·设计模式·桥接模式
model200510 小时前
alibaba linux3 系统盘网站迁移数据盘
java·服务器·前端
荒诞硬汉10 小时前
JavaBean相关补充
java·开发语言
提笔忘字的帝国11 小时前
【教程】macOS 如何完全卸载 Java 开发环境
java·开发语言·macos
2501_9418824811 小时前
从灰度发布到流量切分的互联网工程语法控制与多语言实现实践思路随笔分享
java·开发语言
華勳全栈11 小时前
两天开发完成智能体平台
java·spring·go
alonewolf_9911 小时前
Spring MVC重点功能底层源码深度解析
java·spring·mvc