JVM:第一章:类加载机制深度解析

Java 源代码执行流程

解释:

    启动虚拟机 (C++负责创建) 【windows : bin/java.exe调用 jvm.dll Linux : java 调用 libjvm.so 】

    创建一个引导类加载器实例 (C++实现)

    C++ 调用Java代码,创建JVM启动器,实例sun.misc.Launcher 【这货由引导加载器负责加载创建其他类加载器】

    sun.misc.Launcher.getLauncher() 获取运行类自己的加载器ClassLoader --> 是AppClassLoader

    获取到ClassLoader后调用loadClass(“A”)方法加载运行的类A

    加载完成执行A类的main方法

    程序运行结束

    JVM销毁

loadClass的类加载过程


 

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

谈及比较多的是前五个

    加载:我们说jvm执行的java字节码,编译后在磁盘上,总得读取这个字节码文件吧 ,通过啥读 IO呗 , 所以第一步肯定是加载字节码文件。

    验证:JVM总不能说读到啥就直接运行了吧,你外面有个A.class 里面是一堆JVM规范不认识的内容,也执行不了啊 。 符合JVM规范才能执行后续的步骤,所以第二步是 校验字节码文件的正确性。

    准备:给类的静态变量分配内存,并赋予默认值。 我们的类里,可能会包含一些静态变量吧 。 比如 public static final int a = 12; 得给a分配个默认值 0 ,再比如 public static User user = new User(); 给 static的变量 User分配内存,并赋默认值null (final修饰的常量,直接赋值)。

    解析:这个地方不是很好理解, 解析是什么意思呢?将符号引用替换为直接引用。 符号引用 ? 直接引用? what ? 我们的类的静态方法,比如main方法,其实在Java中有个叫法 都是叫符号 。 这个阶段就会吧,一些静态方法(符号引用,比如刚才说的main方法)替换为指向数据所存内存的指针或者句柄等(直接引用)【找到具体在内存中的位置】。 这个就是静态链接过程(在类加载期间完成)。 动态链接是在程序运行期间完成的将符号引用替换为直接引用 (比如某个普通方法的调用)。

    初始化:上面的步骤完事儿以后,这一步主要是对类的静态变量初始化为指定的值,执行静态代码块。 比如刚才第二步的 public static final int a = 12; ,第二步给static变量赋了默认值,这一步就该把12赋值给它了。 还有 static的 User public static User user = new User(); 实例化User。

说明:主类在运行过程中如果使用到其它类,会逐步加载这些类。jar包或war包里的类不是一次性全部加载的,是使用到时才加载。
类加载顺序示例:
 

    public class TestDynamicLoad {
     
        static {
            System.out.println("加载TestDynamicLoad类中的静态代码块");
        }
     
        public static void main(String[] args) {
            new A();
            System.out.println("执行main方法中的代码");
            B b = null;//B不会加载,除非这里执行 new B()
     
        }
    }
     
    class A{
        static {
            System.out.println("加载A类中的静态代码块");
        }
     
        public A(){
            System.out.println("加载A类中的构造方法");
        }
    }
     
    class B{
        static {
            System.out.println("加载B类中的静态代码块");
        }
     
        public B(){
            System.out.println("加载B类中的构造方法");
        }
    }
     
     
    执行结果:
     
    加载TestDynamicLoad类中的静态代码块
    加载A类中的静态代码块
    加载A类中的构造方法
    执行main方法中的代码

类加载器


 

 

    引导类加载器:负责加载支撑JVM运行的位于JRE的lib目录下的核心类库,比如rt.jar、charsets.jar等
    扩展类加载器:负责加载支撑JVM运行的位于JRE的lib目录下的ext扩展目录中的JAR类包
    应用程序类加载器:负责加载ClassPath路径下的类包,主要就是加载我们应用中自己写的那些类
    自定义加载器:负责加载用户自定义路径下的类包

类加载器示例:

    import com.sun.crypto.provider.DESKeyFactory;
    import sun.misc.Launcher;
    import java.net.URL;
     
    public class TestJDKClassLoader {
     
        public static void main(String[] args) {
     
            System.out.println(String.class.getClassLoader());
            System.out.println(DESKeyFactory.class.getClassLoader().getClass().getName());
            System.out.println(TestJDKClassLoader.class.getClassLoader().getClass().getName());
            System.out.println();
     
            ClassLoader appClassLoader = ClassLoader.getSystemClassLoader();
            ClassLoader extClassloader = appClassLoader.getParent();
            ClassLoader bootstrapLoader = extClassloader.getParent();
            System.out.println("the bootstrapLoader : " + bootstrapLoader);
            System.out.println("the extClassloader : " + extClassloader);
            System.out.println("the appClassLoader : " + appClassLoader);
            System.out.println("AppClassLoader的父类加载器为ExtClassLoader,ExtClassLoader的父类加载器为null,null并不代表ExtClassLoader没有父类加载器,而是 BootstrapClassLoader");
            System.out.println();
            System.out.println("JRE的lib目录下的核心类库,bootstrapLoader加载以下文件:");
            URL[] urls = Launcher.getBootstrapClassPath().getURLs();
            for (int i = 0; i < urls.length; i++) {
                System.out.println(urls[i]);
            }
            System.out.println();
     
            System.out.println("JRE的lib目录下的ext扩展目录中的JAR类包,extClassloader加载以下文件:");
            String[] extClassloaderStr = System.getProperty("java.ext.dirs").split(";");
            for (String s : extClassloaderStr) {
                System.out.println(s);
            }
            System.out.println();
     
            System.out.println("加载ClassPath路径下的类包,appClassLoader加载以下文件:");
            String[] split = System.getProperty("java.class.path").split(";");
            for (String s : split) {
                System.out.println(s);
            }
        }
     
    }
     
    执行结果:
     
    null
    sun.misc.Launcher$ExtClassLoader
    sun.misc.Launcher$AppClassLoader
     
    the bootstrapLoader : null
    the extClassloader : sun.misc.Launcher$ExtClassLoader@3dd3bcd
    the appClassLoader : sun.misc.Launcher$AppClassLoader@14dad5dc
    AppClassLoader的父类加载器为ExtClassLoader,ExtClassLoader的父类加载器为null,null并不代表ExtClassLoader没有父类加载器,而是 BootstrapClassLoader
     
    JRE的lib目录下的核心类库,bootstrapLoader加载以下文件:
    file:/D:/Environment/JDK/lib/resources.jar
    file:/D:/Environment/JDK/lib/rt.jar
    file:/D:/Environment/JDK/lib/sunrsasign.jar
    file:/D:/Environment/JDK/lib/jsse.jar
    file:/D:/Environment/JDK/lib/jce.jar
    file:/D:/Environment/JDK/lib/charsets.jar
    file:/D:/Environment/JDK/lib/jfr.jar
    file:/D:/Environment/JDK/classes
     
    JRE的lib目录下的ext扩展目录中的JAR类包,extClassloader加载以下文件:
    D:\Environment\JDK\lib\ext
    C:\Windows\Sun\Java\lib\ext
     
    加载ClassPath路径下的类包,appClassLoader加载以下文件:
    D:\Environment\JDK\jre\lib\charsets.jar
    D:\Environment\JDK\jre\lib\deploy.jar
    D:\Environment\JDK\jre\lib\ext\access-bridge-64.jar
    D:\Environment\JDK\jre\lib\ext\cldrdata.jar
    D:\Environment\JDK\jre\lib\ext\dnsns.jar
    D:\Environment\JDK\jre\lib\ext\jaccess.jar
    D:\Environment\JDK\jre\lib\ext\jfxrt.jar
    D:\Environment\JDK\jre\lib\ext\localedata.jar
    D:\Environment\JDK\jre\lib\ext\nashorn.jar
    D:\Environment\JDK\jre\lib\ext\sunec.jar
    D:\Environment\JDK\jre\lib\ext\sunjce_provider.jar
    D:\Environment\JDK\jre\lib\ext\sunmscapi.jar
    D:\Environment\JDK\jre\lib\ext\sunpkcs11.jar
    D:\Environment\JDK\jre\lib\ext\zipfs.jar
    D:\Environment\JDK\jre\lib\javaws.jar
    D:\Environment\JDK\jre\lib\jce.jar
    D:\Environment\JDK\jre\lib\jfr.jar
    D:\Environment\JDK\jre\lib\jfxswt.jar
    D:\Environment\JDK\jre\lib\jsse.jar
    D:\Environment\JDK\jre\lib\management-agent.jar
    D:\Environment\JDK\jre\lib\plugin.jar
    D:\Environment\JDK\jre\lib\resources.jar
    D:\Environment\JDK\jre\lib\rt.jar
    D:\Project\Personal\website\reception\target\classes
    D:\Environment\RepMaven\org\springframework\boot\spring-boot-starter\2.2.6.RELEASE\spring-boot-starter-2.2.6.RELEASE.jar
    D:\Environment\RepMaven\org\springframework\boot\spring-boot\2.2.6.RELEASE\spring-boot-2.2.6.RELEASE.jar
    D:\Environment\RepMaven\org\springframework\spring-context\5.2.5.RELEASE\spring-context-5.2.5.RELEASE.jar
    D:\Environment\RepMaven\org\springframework\boot\spring-boot-autoconfigure\2.2.6.RELEASE\spring-boot-autoconfigure-2.2.6.RELEASE.jar
    D:\Environment\RepMaven\org\springframework\boot\spring-boot-starter-logging\2.2.6.RELEASE\spring-boot-starter-logging-2.2.6.RELEASE.jar
    D:\Environment\RepMaven\ch\qos\logback\logback-classic\1.2.3\logback-classic-1.2.3.jar
    D:\Environment\RepMaven\ch\qos\logback\logback-core\1.2.3\logback-core-1.2.3.jar
    D:\Environment\RepMaven\org\apache\logging\log4j\log4j-to-slf4j\2.12.1\log4j-to-slf4j-2.12.1.jar
    D:\Environment\RepMaven\org\apache\logging\log4j\log4j-api\2.12.1\log4j-api-2.12.1.jar
    D:\Environment\RepMaven\org\slf4j\jul-to-slf4j\1.7.30\jul-to-slf4j-1.7.30.jar
    D:\Environment\RepMaven\jakarta\annotation\jakarta.annotation-api\1.3.5\jakarta.annotation-api-1.3.5.jar
    D:\Environment\RepMaven\org\springframework\spring-core\5.2.5.RELEASE\spring-core-5.2.5.RELEASE.jar
    D:\Environment\RepMaven\org\springframework\spring-jcl\5.2.5.RELEASE\spring-jcl-5.2.5.RELEASE.jar
    D:\Environment\RepMaven\org\yaml\snakeyaml\1.25\snakeyaml-1.25.jar
    D:\Environment\RepMaven\org\springframework\boot\spring-boot-starter-web\2.2.6.RELEASE\spring-boot-starter-web-2.2.6.RELEASE.jar
    D:\Environment\RepMaven\org\springframework\boot\spring-boot-starter-json\2.2.6.RELEASE\spring-boot-starter-json-2.2.6.RELEASE.jar
    D:\Environment\RepMaven\com\fasterxml\jackson\core\jackson-databind\2.10.3\jackson-databind-2.10.3.jar
    D:\Environment\RepMaven\com\fasterxml\jackson\core\jackson-core\2.10.3\jackson-core-2.10.3.jar
    D:\Environment\RepMaven\com\fasterxml\jackson\datatype\jackson-datatype-jdk8\2.10.3\jackson-datatype-jdk8-2.10.3.jar
    D:\Environment\RepMaven\com\fasterxml\jackson\datatype\jackson-datatype-jsr310\2.10.3\jackson-datatype-jsr310-2.10.3.jar
    D:\Environment\RepMaven\com\fasterxml\jackson\module\jackson-module-parameter-names\2.10.3\jackson-module-parameter-names-2.10.3.jar
    D:\Environment\RepMaven\org\springframework\boot\spring-boot-starter-validation\2.2.6.RELEASE\spring-boot-starter-validation-2.2.6.RELEASE.jar
    D:\Environment\RepMaven\jakarta\validation\jakarta.validation-api\2.0.2\jakarta.validation-api-2.0.2.jar
    D:\Environment\RepMaven\org\hibernate\validator\hibernate-validator\6.0.18.Final\hibernate-validator-6.0.18.Final.jar
    D:\Environment\RepMaven\org\jboss\logging\jboss-logging\3.4.1.Final\jboss-logging-3.4.1.Final.jar
    D:\Environment\RepMaven\org\springframework\spring-web\5.2.5.RELEASE\spring-web-5.2.5.RELEASE.jar
    D:\Environment\RepMaven\org\springframework\spring-beans\5.2.5.RELEASE\spring-beans-5.2.5.RELEASE.jar
    D:\Environment\RepMaven\org\springframework\spring-webmvc\5.2.5.RELEASE\spring-webmvc-5.2.5.RELEASE.jar
    D:\Environment\RepMaven\org\springframework\spring-aop\5.2.5.RELEASE\spring-aop-5.2.5.RELEASE.jar
    D:\Environment\RepMaven\org\springframework\spring-expression\5.2.5.RELEASE\spring-expression-5.2.5.RELEASE.jar
    D:\Environment\RepMaven\org\apache\tomcat\embed\tomcat-embed-core\9.0.33\tomcat-embed-core-9.0.33.jar
    D:\Environment\RepMaven\org\apache\tomcat\embed\tomcat-embed-el\9.0.33\tomcat-embed-el-9.0.33.jar
    D:\Environment\RepMaven\org\apache\tomcat\embed\tomcat-embed-jasper\9.0.33\tomcat-embed-jasper-9.0.33.jar
    D:\Environment\RepMaven\org\eclipse\jdt\ecj\3.18.0\ecj-3.18.0.jar
    D:\Environment\RepMaven\jstl\jstl\1.2\jstl-1.2.jar
    D:\Environment\RepMaven\mysql\mysql-connector-java\5.1.47\mysql-connector-java-5.1.47.jar
    D:\Environment\RepMaven\tk\mybatis\mapper-spring-boot-starter\2.1.5\mapper-spring-boot-starter-2.1.5.jar
    D:\Environment\RepMaven\org\springframework\boot\spring-boot-starter-jdbc\2.2.6.RELEASE\spring-boot-starter-jdbc-2.2.6.RELEASE.jar
    D:\Environment\RepMaven\com\zaxxer\HikariCP\3.4.2\HikariCP-3.4.2.jar
    D:\Environment\RepMaven\org\springframework\spring-jdbc\5.2.5.RELEASE\spring-jdbc-5.2.5.RELEASE.jar
    D:\Environment\RepMaven\org\mybatis\mybatis\3.4.6\mybatis-3.4.6.jar
    D:\Environment\RepMaven\org\mybatis\mybatis-spring\1.3.2\mybatis-spring-1.3.2.jar
    D:\Environment\RepMaven\tk\mybatis\mapper-core\1.1.5\mapper-core-1.1.5.jar
    D:\Environment\RepMaven\javax\persistence\persistence-api\1.0\persistence-api-1.0.jar
    D:\Environment\RepMaven\tk\mybatis\mapper-base\1.1.5\mapper-base-1.1.5.jar
    D:\Environment\RepMaven\tk\mybatis\mapper-weekend\1.1.5\mapper-weekend-1.1.5.jar
    D:\Environment\RepMaven\tk\mybatis\mapper-spring\1.1.5\mapper-spring-1.1.5.jar
    D:\Environment\RepMaven\tk\mybatis\mapper-extra\1.1.5\mapper-extra-1.1.5.jar
    D:\Environment\RepMaven\tk\mybatis\mapper-spring-boot-autoconfigure\2.1.5\mapper-spring-boot-autoconfigure-2.1.5.jar
    D:\Environment\RepMaven\com\alibaba\druid-spring-boot-starter\1.1.10\druid-spring-boot-starter-1.1.10.jar
    D:\Environment\RepMaven\com\alibaba\druid\1.1.10\druid-1.1.10.jar
    D:\Environment\RepMaven\org\slf4j\slf4j-api\1.7.30\slf4j-api-1.7.30.jar
    D:\Environment\RepMaven\org\mybatis\generator\mybatis-generator-core\1.3.3\mybatis-generator-core-1.3.3.jar
    D:\Environment\RepMaven\io\springfox\springfox-swagger2\2.7.0\springfox-swagger2-2.7.0.jar
    D:\Environment\RepMaven\io\swagger\swagger-annotations\1.5.13\swagger-annotations-1.5.13.jar
    D:\Environment\RepMaven\io\swagger\swagger-models\1.5.13\swagger-models-1.5.13.jar
    D:\Environment\RepMaven\com\fasterxml\jackson\core\jackson-annotations\2.10.3\jackson-annotations-2.10.3.jar
    D:\Environment\RepMaven\io\springfox\springfox-spi\2.7.0\springfox-spi-2.7.0.jar
    D:\Environment\RepMaven\io\springfox\springfox-core\2.7.0\springfox-core-2.7.0.jar
    D:\Environment\RepMaven\net\bytebuddy\byte-buddy\1.10.8\byte-buddy-1.10.8.jar
    D:\Environment\RepMaven\io\springfox\springfox-schema\2.7.0\springfox-schema-2.7.0.jar
    D:\Environment\RepMaven\io\springfox\springfox-swagger-common\2.7.0\springfox-swagger-common-2.7.0.jar
    D:\Environment\RepMaven\io\springfox\springfox-spring-web\2.7.0\springfox-spring-web-2.7.0.jar
    D:\Environment\RepMaven\org\reflections\reflections\0.9.11\reflections-0.9.11.jar
    D:\Environment\RepMaven\org\javassist\javassist\3.21.0-GA\javassist-3.21.0-GA.jar
    D:\Environment\RepMaven\com\google\guava\guava\18.0\guava-18.0.jar
    D:\Environment\RepMaven\com\fasterxml\classmate\1.5.1\classmate-1.5.1.jar
    D:\Environment\RepMaven\org\springframework\plugin\spring-plugin-core\1.2.0.RELEASE\spring-plugin-core-1.2.0.RELEASE.jar
    D:\Environment\RepMaven\org\springframework\plugin\spring-plugin-metadata\1.2.0.RELEASE\spring-plugin-metadata-1.2.0.RELEASE.jar
    D:\Environment\RepMaven\org\mapstruct\mapstruct\1.1.0.Final\mapstruct-1.1.0.Final.jar
    D:\Environment\RepMaven\io\springfox\springfox-swagger-ui\2.7.0\springfox-swagger-ui-2.7.0.jar
    D:\Environment\RepMaven\org\projectlombok\lombok\1.18.12\lombok-1.18.12.jar
    D:\Environment\RepMaven\redis\clients\jedis\3.1.0\jedis-3.1.0.jar
    D:\Environment\RepMaven\org\apache\commons\commons-pool2\2.7.0\commons-pool2-2.7.0.jar
    D:\Environment\RepMaven\org\springframework\boot\spring-boot-starter-amqp\2.2.6.RELEASE\spring-boot-starter-amqp-2.2.6.RELEASE.jar
    D:\Environment\RepMaven\org\springframework\spring-messaging\5.2.5.RELEASE\spring-messaging-5.2.5.RELEASE.jar
    D:\Environment\RepMaven\org\springframework\amqp\spring-rabbit\2.2.5.RELEASE\spring-rabbit-2.2.5.RELEASE.jar
    D:\Environment\RepMaven\com\rabbitmq\amqp-client\5.7.3\amqp-client-5.7.3.jar
    D:\Environment\RepMaven\org\springframework\amqp\spring-amqp\2.2.5.RELEASE\spring-amqp-2.2.5.RELEASE.jar
    D:\Environment\RepMaven\org\springframework\retry\spring-retry\1.2.5.RELEASE\spring-retry-1.2.5.RELEASE.jar
    D:\Environment\RepMaven\org\springframework\spring-tx\5.2.5.RELEASE\spring-tx-5.2.5.RELEASE.jar
    D:\Environment\RepMaven\org\springframework\boot\spring-boot-configuration-processor\2.2.4.RELEASE\spring-boot-configuration-processor-2.2.4.RELEASE.jar
    E:\SoftWare\IntelliJ IDEA 2019.1.1\lib\idea_rt.jar
    C:\Users\root\.IntelliJIdea2019.1\system\captureAgent\debugger-agent.jar

双亲委派机制

什么是双亲委派机制?

    简单的说:先找父亲加载,不行再由儿子自己加载。
    通俗的说: 当我们需要加载某个类时会先委托父加载器寻找目标类,找不到再委托上层父加载器加载,如果所有父加载器在自己的加载类路径下都找不到目标类,则在自己的类加载路径中查找并载入目标类。
    举例说明:我们有个类A.class ,最先会找应用程序类加载器AppClassLoader 加载,AppClassLoader 会先委托扩展类加载器ExtClassLoader加载,扩展类加载器再委托引导类加载器BootClassLoader,顶层引导类加载器BootClassLoader在自己的类加载路径里 没找到A类,则向下退回加载A类的请求,扩展类加载器ExtClassLoader收到回复就自己加载,在自己的类加载路径里找了半天也没找到A类,又向下退回A类的加载请求给应用程序类加载器AppClassLoader ,应用程序类加载器 在自己的类加载路径里找A类,结果找到了就自己加载了。

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

    沙箱安全机制:自己写的java.lang.String.class类不会被加载,这样便可以防止核心API库被随意篡改 。
    避免类的重复加载:当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次,保证被加载类的唯一性。

应用程序类加载器AppClassLoader加载类的双亲委派机制源码

    private final ClassLoader parent;
    protected Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException{
        synchronized (getClassLoadingLock(name)) {
            // 首先,检查请求的类是否已经被加载过
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {
                        //父加载器不为空,调用父加载器loadClass()方法处理
                        c = parent.loadClass(name, false);
                    } else {
                        //父加载器为空,使用启动类加载器 BootstrapClassLoader 加载
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    //抛出异常说明父类加载器无法完成加载请求
                }
     
                if (c == null) {
                    long t1 = System.nanoTime();
                    //自己尝试加载
                    c = findClass(name);
                    // 这是定义类装入器;记录统计信息
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

执行步骤:

    首先,检查一下指定名称的类是否已经加载过,如果加载过了,就不需要再加载,直接返回。
    如果此类没有加载过,那么,再判断一下是否有父加载器;如果有父加载器,则由父加载器加载(即调用parent.loadClass(name, false))或者是调用bootstrap类加载器来加载。
    如果父加载器及bootstrap类加载器都没有找到指定的类,那么调用当前类加载器的findClass方法来完成类加载。

String类加载示例:

    package java.lang;
     
    /**
     * Description: String used for 我自定义的String类
     * Created by root on 2020/8/11 10:33
     */
     
    public class String {
     
        public static void main(String[] args) {
            System.out.println("我自定义的String类");
        }
     
    }
     
    执行结果:
    错误: 在类 java.lang.String 中找不到 main 方法, 请将 main 方法定义为:
       public static void main(String[] args)
    否则 JavaFX 应用程序类必须扩展javafx.application.Application

分析:首先由于全限定类名java.lang.String等于jdk中的String类,当AppClassLoader加载该String时,判断java.lang.String已经加载,便不会再次加载。所以执行的依旧是jdk中的String,但是系统的java.lang.String中没有main()方法,所以会报错。这是一种安全机制。
JVM类加载机制

    全盘负责委托机制,当一个类加载器负责加载某个Class时,该Class所依赖的和引用的其他Class也将由该类加载器负责载入,除非显示使用另外一个类加载器来载入

    双亲委派机制,先让父类加载器试图加载该类,只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类

    缓存机制,缓存机制将会保证所有加载过的Class都会被缓存,当程序中需要使用某个Class时,类加载器先从缓存区寻找该Class,只有缓存区不存在,系统才会读取该类对应的二进制数据,并将其转换成Class对象,存入缓存区。这就是为什么修改了Class后,必须重启JVM,程序的修改才会生效

自定义类加载器示例:
自定义类加载器只需要继承 java.lang.ClassLoader 类,该类有两个核心方法,一个是loadClass(String, boolean),实现了 双亲委派机制 ,还有一个方法是findClass,默认实现是空 方法,所以我们自定义类加载器主要是 重写 findClass 方法 。