JVM(一)——浅析JVM类加载机制及双亲委派机制

目录

类加载过程

类加载步骤

类加载器

引导类加载器(启动类加载器)——BootstrapClassLoader

扩展类加载器——ExtClassLoader

应用程序类加载器——AppClassLoader

自定义加载器

类加载器初始化过程

双亲委派机制

双亲委派源码剖析

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

自定义类加载器

如何打破双亲委派

打破双亲委派的场景

Tomcat容器如何打破双亲委派机制


类加载过程

将java文件打成jar包或者war包之后,由某个主类的main函数进行启动,需要通过类加载器把主类加载到JVM,jar包里的类不是一次性加载,是在使用到的时候才会加载。

public class LoadClassTest {
    static {
        System.out.println("*************Load Class LoadClassTest************");
    }

    public static void main(String[] args) {
        System.out.println("*************LOAD A START************");
        new A();
        System.out.println("*************LOAD A END************");
        System.out.println("*************LOAD B START************");
        B b = null;//是不加载的,只有在执行new B()的时候才会加载
        //B b = new B();
        System.out.println("*************LOAD B END************");
    }
}

class A {
    static {
        System.out.println("*************Load Class A************");
    }

    public A(){
        System.out.println("*************Init Class A************");
    }
}

class B {
    static {
        System.out.println("*************Load Class B************");
    }

    public B(){
        System.out.println("*************Init Class B************");
    }
}

运行结果:

*************Load LoadClassTest Class************
*************LOAD A START************
*************Load Class A************
*************Init Class A************
*************LOAD A END************

*************LOAD B START************
*************LOAD B END************

类加载步骤

加载:在硬盘上查找并通过IO读取字节码文件

验证:验证字节码文件正确性

准备:给类的静态变量分配内存,并赋默认值。例如:int=0、boolean=false、对象=null

解析:将静态方法符号引用替换为直接引用——静态链接;程序运行期间完成符号引用替换为直接引用——动态链接

初始化:对类的静态变量初始化为指定的值,执行静态代码块

类加载器

引导类加载器(启动类加载器)——BootstrapClassLoader

由C++实现,嵌入在JVM内部,负责加载JRE的lib目录下的核心类库,比如rt,jar、charsets.jar等

扩展类加载器——ExtClassLoader

由Java代码实现,在JVM启动器实例(sun.misc.Lanucher)初始化时创建,是负责加载JRE的lib目录下ext的jar包

应用程序类加载器——AppClassLoader

由Java代码实现,在JVM启动器实例(sun.misc.Lanucher)初始化时创建,负责加载ClassPath路径下的类包,主要就是加载自己写的类

自定义加载器

负责加载自定义路径下的类包

public class TestClassLoader {

    public static void main(String[] args) {
        System.out.println(String.class.getClassLoader());
        System.out.println(com.sun.crypto.provider.DESKeyFactory.class.getClassLoader().getClass().getName());
        System.out.println(TestClassLoader.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();
        System.out.println("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("extClassloader加载以下文件:");
        System.out.println(System.getProperty("java.ext.dirs"));

        System.out.println();
        System.out.println("appClassLoader加载以下文件:");
        System.out.println(System.getProperty("java.class.path"));

    }
}

运行结果:

null
sun.misc.Launcher$ExtClassLoader
sun.misc.Launcher$AppClassLoader

the bootstrapLoader : null
the extClassloader : sun.misc.Launcher$ExtClassLoader@497470ed
the appClassLoader : sun.misc.Launcher$AppClassLoader@18b4aac2

bootstrapLoader加载以下文件:
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_261.jdk/Contents/Home/jre/lib/resources.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_261.jdk/Contents/Home/jre/lib/rt.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_261.jdk/Contents/Home/jre/lib/sunrsasign.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_261.jdk/Contents/Home/jre/lib/jsse.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_261.jdk/Contents/Home/jre/lib/jce.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_261.jdk/Contents/Home/jre/lib/charsets.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_261.jdk/Contents/Home/jre/lib/jfr.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_261.jdk/Contents/Home/jre/classes

extClassloader加载以下文件:
/Users/yyyz/Library/Java/Extensions:/Library/Java/JavaVirtualMachines/jdk1.8.0_261.jdk/Contents/Home/jre/lib/ext:/Library/Java/Extensions:/Network/Library/Java/Extensions:/System/Library/Java/Extensions:/usr/lib/java

appClassLoader加载以下文件:
/Library/Java/JavaVirtualMachines/jdk1.8.0_261.jdk/Contents/Home/jre/lib/charsets.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_261.jdk/Contents/Home/jre/lib/deploy.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_261.jdk/Contents/Home/jre/lib/ext/cldrdata.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_261.jdk/Contents/Home/jre/lib/ext/dnsns.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_261.jdk/Contents/Home/jre/lib/ext/jaccess.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_261.jdk/Contents/Home/jre/lib/ext/jfxrt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_261.jdk/Contents/Home/jre/lib/ext/localedata.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_261.jdk/Contents/Home/jre/lib/ext/nashorn.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_261.jdk/Contents/Home/jre/lib/ext/sunec.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_261.jdk/Contents/Home/jre/lib/ext/sunjce_provider.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_261.jdk/Contents/Home/jre/lib/ext/sunpkcs11.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_261.jdk/Contents/Home/jre/lib/ext/zipfs.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_261.jdk/Contents/Home/jre/lib/javaws.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_261.jdk/Contents/Home/jre/lib/jce.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_261.jdk/Contents/Home/jre/lib/jfr.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_261.jdk/Contents/Home/jre/lib/jfxswt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_261.jdk/Contents/Home/jre/lib/jsse.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_261.jdk/Contents/Home/jre/lib/management-agent.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_261.jdk/Contents/Home/jre/lib/plugin.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_261.jdk/Contents/Home/jre/lib/resources.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_261.jdk/Contents/Home/jre/lib/rt.jar:/Users/yyyz/IdeaProjects/zsy-demo/target/classes:/Users/yyyz/.m2/repository/org/springframework/boot/spring-boot-starter-amqp/2.7.7/spring-boot-starter-amqp-2.7.7.jar:/Users/yyyz/.m2/repository/org/springframework/boot/spring-boot-starter/2.7.7/spring-boot-starter-2.7.7.jar:/Users/yyyz/.m2/repository/org/springframework/boot/spring-boot-starter-logging/2.7.7/spring-boot-starter-logging-2.7.7.jar:/Users/yyyz/.m2/repository/ch/qos/logback/logback-classic/1.2.11/logback-classic-1.2.11.jar:/Users/yyyz/.m2/repository/ch/qos/logback/logback-core/1.2.11/logback-core-1.2.11.jar:/Users/yyyz/.m2/repository/org/apache/logging/log4j/log4j-to-slf4j/2.17.2/log4j-to-slf4j-2.17.2.jar:/Users/yyyz/.m2/repository/org/apache/logging/log4j/log4j-api/2.17.2/log4j-api-2.17.2.jar:/Users/yyyz/.m2/repository/org/slf4j/jul-to-slf4j/1.7.36/jul-to-slf4j-1.7.36.jar:/Users/yyyz/.m2/repository/jakarta/annotation/jakarta.annotation-api/1.3.5/jakarta.annotation-api-1.3.5.jar:/Users/yyyz/.m2/repository/org/yaml/snakeyaml/1.30/snakeyaml-1.30.jar:/Users/yyyz/.m2/repository/org/springframework/spring-messaging/5.3.24/spring-messaging-5.3.24.jar:/Users/yyyz/.m2/repository/org/springframework/spring-beans/5.3.24/spring-beans-5.3.24.jar:/Users/yyyz/.m2/repository/org/springframework/amqp/spring-rabbit/2.4.8/spring-rabbit-2.4.8.jar:/Users/yyyz/.m2/repository/org/springframework/amqp/spring-amqp/2.4.8/spring-amqp-2.4.8.jar:/Users/yyyz/.m2/repository/org/springframework/retry/spring-retry/1.3.4/spring-retry-1.3.4.jar:/Users/yyyz/.m2/repository/com/rabbitmq/amqp-client/5.14.2/amqp-client-5.14.2.jar:/Users/yyyz/.m2/repository/org/springframework/spring-context/5.3.24/spring-context-5.3.24.jar:/Users/yyyz/.m2/repository/org/springframework/spring-tx/5.3.24/spring-tx-5.3.24.jar:/Users/yyyz/.m2/repository/org/springframework/boot/spring-boot-starter-data-elasticsearch/2.7.7/spring-boot-starter-data-elasticsearch-2.7.7.jar:/Users/yyyz/.m2/repository/org/springframework/data/spring-data-elasticsearch/4.4.6/spring-data-elasticsearch-4.4.6.jar:/Users/yyyz/.m2/repository/org/springframework/data/spring-data-commons/2.7.6/spring-data-commons-2.7.6.jar:/Users/yyyz/.m2/repository/org/elasticsearch/client/elasticsearch-rest-high-level-client/7.17.8/elasticsearch-rest-high-level-client-7.17.8.jar:/Users/yyyz/.m2/repository/org/elasticsearch/elasticsearch/7.17.8/elasticsearch-7.17.8.jar:/Users/yyyz/.m2/repository/org/elasticsearch/elasticsearch-core/7.17.8/elasticsearch-core-7.17.8.jar:/Users/yyyz/.m2/repository/org/elasticsearch/elasticsearch-secure-sm/7.17.8/elasticsearch-secure-sm-7.17.8.jar:/Users/yyyz/.m2/repository/org/elasticsearch/elasticsearch-x-content/7.17.8/elasticsearch-x-content-7.17.8.jar:/Users/yyyz/.m2/repository/com/fasterxml/jackson/dataformat/jackson-dataformat-smile/2.13.4/jackson-dataformat-smile-2.13.4.jar:/Users/yyyz/.m2/repository/com/fasterxml/jackson/dataformat/jackson-dataformat-yaml/2.13.4/jackson-dataformat-yaml-2.13.4.jar:/Users/yyyz/.m2/repository/com/fasterxml/jackson/dataformat/jackson-dataformat-cbor/2.13.4/jackson-dataformat-cbor-2.13.4.jar:/Users/yyyz/.m2/repository/org/elasticsearch/elasticsearch-geo/7.17.8/elasticsearch-geo-7.17.8.jar:/Users/yyyz/.m2/repository/org/elasticsearch/elasticsearch-lz4/7.17.8/elasticsearch-lz4-7.17.8.jar:/Users/yyyz/.m2/repository/org/lz4/lz4-java/1.8.0/lz4-java-1.8.0.jar:/Users/yyyz/.m2/repository/org/apache/lucene/lucene-core/8.11.1/lucene-core-8.11.1.jar:/Users/yyyz/.m2/repository/org/apache/lucene/lucene-analyzers-common/8.11.1/lucene-analyzers-common-8.11.1.jar:/Users/yyyz/.m2/repository/org/apache/lucene/lucene-backward-codecs/8.11.1/lucene-backward-codecs-8.11.1.jar:/Users/yyyz/.m2/repository/org/apache/lucene/lucene-grouping/8.11.1/lucene-grouping-8.11.1.jar:/Users/yyyz/.m2/repository/org/apache/lucene/lucene-highlighter/8.11.1/lucene-highlighter-8.11.1.jar:/Users/yyyz/.m2/repository/org/apache/lucene/lucene-join/8.11.1/lucene-join-8.11.1.jar:/Users/yyyz/.m2/repository/org/apache/lucene/lucene-memory/8.11.1/lucene-memory-8.11.1.jar:/Users/yyyz/.m2/repository/org/apache/lucene/lucene-misc/8.11.1/lucene-misc-8.11.1.jar:/Users/yyyz/.m2/repository/org/apache/lucene/lucene-queries/8.11.1/lucene-queries-8.11.1.jar:/Users/yyyz/.m2/repository/org/apache/lucene/lucene-queryparser/8.11.1/lucene-queryparser-8.11.1.jar:/Users/yyyz/.m2/repository/org/apache/lucene/lucene-sandbox/8.11.1/lucene-sandbox-8.11.1.jar:/Users/yyyz/.m2/repository/org/apache/lucene/lucene-spatial3d/8.11.1/lucene-spatial3d-8.11.1.jar:/Users/yyyz/.m2/repository/org/apache/lucene/lucene-suggest/8.11.1/lucene-suggest-8.11.1.jar:/Users/yyyz/.m2/repository/org/elasticsearch/elasticsearch-cli/7.17.8/elasticsearch-cli-7.17.8.jar:/Users/yyyz/.m2/repository/net/sf/jopt-simple/jopt-simple/5.0.2/jopt-simple-5.0.2.jar:/Users/yyyz/.m2/repository/com/carrotsearch/hppc/0.8.1/hppc-0.8.1.jar:/Users/yyyz/.m2/repository/joda-time/joda-time/2.10.10/joda-time-2.10.10.jar:/Users/yyyz/.m2/repository/com/tdunning/t-digest/3.2/t-digest-3.2.jar:/Users/yyyz/.m2/repository/org/hdrhistogram/HdrHistogram/2.1.9/HdrHistogram-2.1.9.jar:/Users/yyyz/.m2/repository/net/java/dev/jna/jna/5.10.0/jna-5.10.0.jar:/Users/yyyz/.m2/repository/org/elasticsearch/elasticsearch-plugin-classloader/7.17.8/elasticsearch-plugin-classloader-7.17.8.jar:/Users/yyyz/.m2/repository/org/elasticsearch/plugin/mapper-extras-client/7.17.8/mapper-extras-client-7.17.8.jar:/Users/yyyz/.m2/repository/org/elasticsearch/plugin/parent-join-client/7.17.8/parent-join-client-7.17.8.jar:/Users/yyyz/.m2/repository/org/elasticsearch/plugin/aggs-matrix-stats-client/7.17.8/aggs-matrix-stats-client-7.17.8.jar:/Users/yyyz/.m2/repository/org/elasticsearch/plugin/rank-eval-client/7.17.8/rank-eval-client-7.17.8.jar:/Users/yyyz/.m2/repository/org/elasticsearch/plugin/lang-mustache-client/7.17.8/lang-mustache-client-7.17.8.jar:/Users/yyyz/.m2/repository/com/github/spullara/mustache/java/compiler/0.9.6/compiler-0.9.6.jar:/Users/yyyz/.m2/repository/co/elastic/clients/elasticsearch-java/7.17.7/elasticsearch-java-7.17.7.jar:/Users/yyyz/.m2/repository/com/google/code/findbugs/jsr305/3.0.2/jsr305-3.0.2.jar:/Users/yyyz/.m2/repository/jakarta/json/jakarta.json-api/1.1.6/jakarta.json-api-1.1.6.jar:/Users/yyyz/.m2/repository/org/eclipse/parsson/parsson/1.0.0/parsson-1.0.0.jar:/Users/yyyz/.m2/repository/org/elasticsearch/client/elasticsearch-rest-client/7.17.8/elasticsearch-rest-client-7.17.8.jar:/Users/yyyz/.m2/repository/org/apache/httpcomponents/httpcore/4.4.16/httpcore-4.4.16.jar:/Users/yyyz/.m2/repository/org/apache/httpcomponents/httpasyncclient/4.1.5/httpasyncclient-4.1.5.jar:/Users/yyyz/.m2/repository/org/apache/httpcomponents/httpcore-nio/4.4.16/httpcore-nio-4.4.16.jar:/Users/yyyz/.m2/repository/commons-codec/commons-codec/1.15/commons-codec-1.15.jar:/Users/yyyz/.m2/repository/com/fasterxml/jackson/core/jackson-core/2.13.4/jackson-core-2.13.4.jar:/Users/yyyz/.m2/repository/com/fasterxml/jackson/core/jackson-databind/2.13.4.2/jackson-databind-2.13.4.2.jar:/Users/yyyz/.m2/repository/com/fasterxml/jackson/core/jackson-annotations/2.13.4/jackson-annotations-2.13.4.jar:/Users/yyyz/.m2/repository/org/slf4j/slf4j-api/1.7.36/slf4j-api-1.7.36.jar:/Users/yyyz/.m2/repository/org/springframework/boot/spring-boot-starter-data-redis/2.7.7/spring-boot-starter-data-redis-2.7.7.jar:/Users/yyyz/.m2/repository/org/springframework/data/spring-data-redis/2.7.6/spring-data-redis-2.7.6.jar:/Users/yyyz/.m2/repository/org/springframework/data/spring-data-keyvalue/2.7.6/spring-data-keyvalue-2.7.6.jar:/Users/yyyz/.m2/repository/org/springframework/spring-oxm/5.3.24/spring-oxm-5.3.24.jar:/Users/yyyz/.m2/repository/org/springframework/spring-aop/5.3.24/spring-aop-5.3.24.jar:/Users/yyyz/.m2/repository/org/springframework/spring-context-support/5.3.24/spring-context-support-5.3.24.jar:/Users/yyyz/.m2/repository/io/lettuce/lettuce-core/6.1.10.RELEASE/lettuce-core-6.1.10.RELEASE.jar:/Users/yyyz/.m2/repository/io/netty/netty-common/4.1.86.Final/netty-common-4.1.86.Final.jar:/Users/yyyz/.m2/repository/io/netty/netty-handler/4.1.86.Final/netty-handler-4.1.86.Final.jar:/Users/yyyz/.m2/repository/io/netty/netty-resolver/4.1.86.Final/netty-resolver-4.1.86.Final.jar:/Users/yyyz/.m2/repository/io/netty/netty-buffer/4.1.86.Final/netty-buffer-4.1.86.Final.jar:/Users/yyyz/.m2/repository/io/netty/netty-transport-native-unix-common/4.1.86.Final/netty-transport-native-unix-common-4.1.86.Final.jar:/Users/yyyz/.m2/repository/io/netty/netty-codec/4.1.86.Final/netty-codec-4.1.86.Final.jar:/Users/yyyz/.m2/repository/io/netty/netty-transport/4.1.86.Final/netty-transport-4.1.86.Final.jar:/Users/yyyz/.m2/repository/io/projectreactor/reactor-core/3.4.26/reactor-core-3.4.26.jar:/Users/yyyz/.m2/repository/org/reactivestreams/reactive-streams/1.0.4/reactive-streams-1.0.4.jar:/Users/yyyz/.m2/repository/org/springframework/boot/spring-boot-starter-web/2.7.7/spring-boot-starter-web-2.7.7.jar:/Users/yyyz/.m2/repository/org/springframework/boot/spring-boot-starter-json/2.7.7/spring-boot-starter-json-2.7.7.jar:/Users/yyyz/.m2/repository/com/fasterxml/jackson/datatype/jackson-datatype-jdk8/2.13.4/jackson-datatype-jdk8-2.13.4.jar:/Users/yyyz/.m2/repository/com/fasterxml/jackson/datatype/jackson-datatype-jsr310/2.13.4/jackson-datatype-jsr310-2.13.4.jar:/Users/yyyz/.m2/repository/com/fasterxml/jackson/module/jackson-module-parameter-names/2.13.4/jackson-module-parameter-names-2.13.4.jar:/Users/yyyz/.m2/repository/org/springframework/boot/spring-boot-starter-tomcat/2.7.7/spring-boot-starter-tomcat-2.7.7.jar:/Users/yyyz/.m2/repository/org/apache/tomcat/embed/tomcat-embed-core/9.0.70/tomcat-embed-core-9.0.70.jar:/Users/yyyz/.m2/repository/org/apache/tomcat/embed/tomcat-embed-el/9.0.70/tomcat-embed-el-9.0.70.jar:/Users/yyyz/.m2/repository/org/apache/tomcat/embed/tomcat-embed-websocket/9.0.70/tomcat-embed-websocket-9.0.70.jar:/Users/yyyz/.m2/repository/org/springframework/spring-web/5.3.24/spring-web-5.3.24.jar:/Users/yyyz/.m2/repository/org/springframework/spring-webmvc/5.3.24/spring-webmvc-5.3.24.jar:/Users/yyyz/.m2/repository/org/springframework/spring-expression/5.3.24/spring-expression-5.3.24.jar:/Users/yyyz/.m2/repository/org/springframework/boot/spring-boot-devtools/2.7.7/spring-boot-devtools-2.7.7.jar:/Users/yyyz/.m2/repository/org/springframework/boot/spring-boot/2.7.7/spring-boot-2.7.7.jar:/Users/yyyz/.m2/repository/org/springframework/boot/spring-boot-autoconfigure/2.7.7/spring-boot-autoconfigure-2.7.7.jar:/Users/yyyz/.m2/repository/com/mysql/mysql-connector-j/8.0.31/mysql-connector-j-8.0.31.jar:/Users/yyyz/.m2/repository/org/projectlombok/lombok/1.18.24/lombok-1.18.24.jar:/Users/yyyz/.m2/repository/org/springframework/spring-core/5.3.24/spring-core-5.3.24.jar:/Users/yyyz/.m2/repository/org/springframework/spring-jcl/5.3.24/spring-jcl-5.3.24.jar:/Users/yyyz/.m2/repository/org/apache/httpcomponents/httpclient/4.5.14/httpclient-4.5.14.jar:/Applications/IntelliJ IDEA.app/Contents/lib/idea_rt.jar

类加载器初始化过程

底层C++代码调用Java代码创建JVM启动器实例sum.misc.Launcher,Launcher初始化使用了单例模式,保证JVM虚拟机只有一个sum.misc.Launcher实例。

在Launcher构造方法内部创建了两个类加载器,一个是扩展类加载器,一个是应用类加载器。JVM默认使用getAppClassLoader()方法返回的类加载器来加载我们的应用程序

示例代码如下:

public Launcher() {
        Launcher.ExtClassLoader var1;
        try {
            //构造扩展类加载器ExtClassLoader
            var1 = Launcher.ExtClassLoader.getExtClassLoader();
        } catch (IOException var10) {
            throw new InternalError("Could not create extension class loader", var10);
        }

        try {
            //构造应用类加载器AppClassLoader,并设置父类加载器为ExtClassLoader,设置Launcher的loader属性为AppClassLoader
            this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
        } catch (IOException var9) {
            throw new InternalError("Could not create application class loader", var9);
        }

        Thread.currentThread().setContextClassLoader(this.loader);
        String var2 = System.getProperty("java.security.manager");

    }

双亲委派机制

双亲委派机制就是在加载某个类时会先委托父类加载器去加载,如果父类加载器在自己的加载路径中未查到需要加载的类,则再由子类加载器加载路径中查找加载

双亲委派源码剖析

下面我们来看下应用类加载器AppClassLoader加载类的双亲委派机制源码,源码如下:

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) {
                        //若父类加载器不为空,则由父类加载器加载
                        c = parent.loadClass(name, false);
                    } else {
                        //若父类加载器为空,则由引导类加载器BootstrapClassLoader加载()
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {
                    // 如果仍未找到,则调用当前类加载器的findClass方法继续查找
                    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;
        }
    }

结合源码双亲委派大致步骤如下:

  1. 首先先判断类是否被加载过,如果被加载过直接返回
  2. 如果没有被加载过,如果有父类加载器则有父类加载,如果没有父类加载器则有JVM内置的引导类加载器进行加载
  3. 如果父类加载器及引导类加载器(BootstrapClassLoader)都未查找到指定类,则由调用当前的类加载器的findClass方法自行加载

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

  • 沙箱安全机制:防止核心类库被篡改
  • 避免类的重复加载:父类已经加载了该类,没必要子类再加载一次,保证被加载类的唯一性

自定义类加载器

自定义加载器只需要集成ClassLoader类,这个类有两个核心方法,一个是loadClass方法实现了双亲委派机制、一个是findClass方法默认,自定义类加载器主要就是重写findClass方法。

示例代码如下:

public class MyClassLoaderTest  {

    @AllArgsConstructor
    @Data
    static class MyClassLoader extends ClassLoader{
        private String classPath;

        public Class<?> findClass(String name) throws ClassNotFoundException {
            try {
                byte[] data = loadByte(name);
                return defineClass(name, data, 0, data.length);
            } catch (Exception e) {
                throw new ClassNotFoundException();
            }
        }

        private byte[] loadByte(String name) throws Exception {
            name = name.replaceAll("\\.", "/");
            FileInputStream inputStream = new FileInputStream(classPath + "/" + name + ".class");
            int length = inputStream.available();
            byte[] data = new byte[length];
            inputStream.read(data);
            inputStream.close();
            return data;
        }
    }

    public static void main(String[] args) throws Exception {

        //自定义类加载器加载路径
        MyClassLoader myClassLoader = new MyClassLoader("/Users/yyyz/env/jvmtest");
        //类加载器加载指定类,将指定加载类的class文件放在指定的加载路径中
        Class clazz = myClassLoader.loadClass("com.demo.zsydemo.jvm.Test");
        Object object = clazz.newInstance();
        Method method = object.getClass().getMethod("sout");
        method.invoke(object,null);
        //输出当前类加载器是否是自定义的类加载器
        System.out.println(clazz.getClassLoader().getClass().getName());
    }

}
public class Test {
    public static void sout(){
        System.out.println("这是自定义类加载器加载的");
    }
}

运行结果:

这是自定义类加载器加载的
com.demo.zsydemo.jvm.MyClassLoaderTest$MyClassLoader

如何打破双亲委派

类加载器中实现双亲委派的方法是loadClass方法,我们只需在掐面写的自定义类加载器中重写一下loadClass方法,干掉原生loadClass方法中的向上委托的地方即可打破双亲委派机制。下面是尝试打破双亲委派机制,用自定义类加载器加载自己实现的java.lang.String.class的示例代码如下:

public class MyClassLoaderTest {

    @AllArgsConstructor
    @Data
    static class MyClassLoader extends ClassLoader {
        private String classPath;

        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();
                    //这块是向上委托的逻辑干掉这块逻辑即可打破双亲委派
                    //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
                    //}
                    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;
            }
        }

        public Class<?> findClass(String name) throws ClassNotFoundException {
            try {
                byte[] data = loadByte(name);
                return defineClass(name, data, 0, data.length);
            } catch (Exception e) {
                e.printStackTrace();
                throw new ClassNotFoundException();
            }
        }

        private byte[] loadByte(String name) throws Exception {
            name = name.replaceAll("\\.", "/");
            FileInputStream inputStream = new FileInputStream(classPath + "/" + name + ".class");
            int length = inputStream.available();
            byte[] data = new byte[length];
            inputStream.read(data);
            inputStream.close();
            return data;
        }
    }

    public static void main(String[] args) throws Exception {
        MyClassLoader myClassLoader = new MyClassLoader("/Users/yyyz/env/jvmtest");
        Class clazz = myClassLoader.loadClass("java.lang.String");
        Object object = clazz.newInstance();
        Method method = object.getClass().getMethod("replace");
        method.invoke(object, null);
        System.out.println(clazz.getClassLoader().getClass().getName());
    }

}

运行结果:

java.lang.SecurityException: Prohibited package name: java.lang
    at java.lang.ClassLoader.preDefineClass(ClassLoader.java:655)
    at java.lang.ClassLoader.defineClass(ClassLoader.java:754)
    at java.lang.ClassLoader.defineClass(ClassLoader.java:635)
    at com.demo.zsydemo.jvm.MyClassLoaderTest$MyClassLoader.findClass(MyClassLoaderTest.java:66)
    at com.demo.zsydemo.jvm.MyClassLoaderTest$MyClassLoader.loadClass(MyClassLoaderTest.java:48)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:351)
    at com.demo.zsydemo.jvm.MyClassLoaderTest.main(MyClassLoaderTest.java:86)
Exception in thread "main" java.lang.ClassNotFoundException
    at com.demo.zsydemo.jvm.MyClassLoaderTest$MyClassLoader.findClass(MyClassLoaderTest.java:69)
    at com.demo.zsydemo.jvm.MyClassLoaderTest$MyClassLoader.loadClass(MyClassLoaderTest.java:48)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:351)
    at com.demo.zsydemo.jvm.MyClassLoaderTest.main(MyClassLoaderTest.java:86)

这个异常是返回的安全异常,禁止加载java.lang包,这个就是前面讲的沙箱安全机制,这个机制可以防止核心类库被篡改。

打破双亲委派的场景

Tomcat容器如何打破双亲委派机制

tomcat是一个web容器,它需要解决什么问题:

  1. 一个web容器可能需要部署多个应用,不同的应用程序可能会依赖同一个第三方类库的不同版本,不能要求同一个类库在同一个服务器只有一份,因此要保证每个类库都是独立的,保证相互隔离
  2. 部署在同一个web容器中,相同的类库的相同版本可以共享
  3. web容器也有自己依赖的类库,不能需应用程序的类库混淆,需要将容器类库与应用程序类库保持隔离
  4. web容器需要支持jsp的修改,需要支持修改jsp后不重启

基于上面四个问题,我们可以看下tomcat为什么要打破双亲委派

第一个问题,如果使用默认的类加载机制,是不支持加载相同类库不同版本的,默认类加载器需要确保被加载类的唯一性

第二个问题,默认类加载器可以实现,默认类加载器的职责就是唯一的

第三个问题和第一个问题一样

第四个问题,如果要实现jsp文件的热加载,jsp文件其实也就是class文件,如果文件修改,类名称不变,默认类加载器还是会去直接取方法区中已经存在的,修改后的jsp文件是不会被重新加载的,每个jsp文件都会对应一个jsp类加载器,如果jsp被修改了,就会直接卸载这个jsp类加载器,重新创建类加载器,重新加载jsp文件

Tomcat主要类加载器:

  • CommonClassLoader:Tomcat最基本的类加载器,加载路径的class对web容器和各个webapp都可见
  • CatalinaClassLoader:web容器私有的类加载器,加载路径的class只对web容器可见
  • SharedClassLoader:各个webapp共享的类加载器,加载路径的class对所有webapp可见,对web容器不可见
  • WebappClassLoader:各个webapp的私有类加载器,加载路径的class只对当前webapp可见

通过上图的委派关系中可以看出:

  • CommonClassLoader加载的类也都能被CatalinaClassLoader和SharedClassLoader使用,实现了公有库公用的问题
  • CatalinaClassLoader和SharedClassLoader自己能加载的类对对方相互隔离
  • WebappClassLoader可以使用SharedClassLoader加载的类,每个war包都有自己对应的WebappClassLoader,各个实例之间可以实现相互隔离
  • JaspLoader加载范围仅仅是这个jsp所编译出来的那一个class文件,出现的目的就是为了被丢弃,每当jsp文件被修改,会替换到当前JaspLoader的实例,并通过在新建一个新的jsp类加载器实现jsp的热加载功能

tomcat这种为了实现隔离性,WebappClassLoader加载自己目录下的class文件,不会传递给父类加载器加载,打破了双亲委派机制

下面是模拟tomcat实现WebappClassLoader加载自己war包应用内不同版本类实现相互共存与隔离,也是基于上面的打破双亲委派的自定义类加载器实现的,代码示例如下:

public class MyClassLoaderTest {

    @AllArgsConstructor
    @Data
    static class MyClassLoader extends ClassLoader {
        private String classPath;

        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();
                    //这块是向上委托的逻辑干掉这块逻辑即可打破双亲委派
                    //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
                    //}
                    if (c == null) {
                        // If still not found, then invoke findClass in order
                        // to find the class.
                        long t1 = System.nanoTime();
                        if (!name.startsWith("com.demo.zsydemo.jvm")) {
                            c = this.getParent().loadClass(name);
                        } else {
                            c = findClass(name);
                        }


                        // this is the defining class loader; record the stats
                        PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                        PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                        PerfCounter.getFindClasses().increment();
                    }
                }
                if (resolve) {
                    resolveClass(c);
                }
                return c;
            }
        }

        public Class<?> findClass(String name) throws ClassNotFoundException {
            try {
                byte[] data = loadByte(name);
                return defineClass(name, data, 0, data.length);
            } catch (Exception e) {
                e.printStackTrace();
                throw new ClassNotFoundException();
            }
        }

        private byte[] loadByte(String name) throws Exception {
            name = name.replaceAll("\\.", "/");
            FileInputStream inputStream = new FileInputStream(classPath + "/" + name + ".class");
            int length = inputStream.available();
            byte[] data = new byte[length];
            inputStream.read(data);
            inputStream.close();
            return data;
        }
    }

    public static void main(String[] args) throws Exception {
        MyClassLoader myClassLoader = new MyClassLoader("/Users/yyyz/env/jvmtest");
        Class clazz = myClassLoader.loadClass("com.demo.zsydemo.jvm.TomcatTest");
        Object object = clazz.newInstance();
        Method method = object.getClass().getMethod("sout");
        method.invoke(object, null);
        System.out.println(clazz.getClassLoader().getClass().getName());

        System.out.println("--------------分割线--------------");

        MyClassLoader myClassLoader1= new MyClassLoader("/Users/yyyz/env/jvmtest01");
        Class clazz1 = myClassLoader1.loadClass("com.demo.zsydemo.jvm.TomcatTest");
        Object object1 = clazz1.newInstance();
        Method method1 = object1.getClass().getMethod("sout");
        method1.invoke(object1, null);
        System.out.println(clazz.getClassLoader().getClass().getName());
    }

}

运行结果:

模拟tomcat测试类——TomcatTest
com.demo.zsydemo.jvm.MyClassLoaderTest$MyClassLoader
--------------分割线--------------
另外一个模拟tomcat测试类——TomcatTest
com.demo.zsydemo.jvm.MyClassLoaderTest$MyClassLoader

同一个JVM内,两个相同包名和类名的类对象可以共存,因为他们的类加载器可能是不一样,所以看两个对象是否是同一个,除了看类名和包名是否是同一个,还要看类加载器是否是同一个。​​​​​​​

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值