Java类加载器与双亲委派机制和线程上下文类加载器专项解读分析

目录
  • 一、类加载器
    • 1.启动类加载器
    • 2.拓展类加载器
    • 3.应用类加载器
    • 4.类的命名空间
  • 二、双亲委派机制
    • 1.类加载机制流程
    • 2.类加载器加载顺序
    • 3.双亲委派机制流程
    • 4.源码分析
    • 5.双亲委派机制优缺点
  • 三、线程上下文类加载器
    • 1.线程上下文类加载器(Context Classloader)
    • 2.ServiceLoader
  • 四、自定义类加载器

一、类加载器

类加载器就是根据类的二进制名(binary name)读取java编译器编译好的字节码文件(.class文件),并且转化生成一个java.lang.Class类的一个实例。

每个实例用来表示一个Java类,jvm就是用这些实例来生成java对象的。

如new一个String对象;反射生成一个String对象,都会用到String.class 这个java.lang.Class类的对象。

基本上所有的类加载器都是java.lang.ClassLoader 类的一个实例

类加载器3大分类

类加载器 加载类 说明
启动类加载器(Bootstrap ClassLoader) JAVA_HOME/jre/lib 无法直接访问
拓展类加载器(Extension ClassLoader) JAVA_HOME/jre/lib/ext 上级为 Bootstrap,显示为 null
应用类加载器(Application ClassLoader) classpath 上级为 Extension
自定义类加载器 自定义 上级为 Application

类加载器加载顺序如下

类加载器的核心方法

方法名 说明
getParent() 返回该类加载器的父类加载器
loadClass(String name) 加载名为name的类,返回java.lang.Class类的实例
findClass(String name) 查找名字为name的类,返回的结果是java.lang.Class类的实例
findLoadedClass(String name) 查找名字为name的已经被加载过的类,返回的结果是java.lang.Class类的实例
defineClass(String name,byte[] b,int off,int len) 根据字节数组b中的数据转化成Java类,返回的结果是java.lang.Class类的实例

上述方法的name参数都是binary name(类的二进制名字),如

java.lang.String <包名>.<类名>

java.concurrent.locks.AbstractQueuedSynchronizer$Node <包名>.<类名>$<内部类名>

java.net.URLClassLoader$1 <包名>.<类名>.<匿名内部类名>

1.启动类加载器

启动类加载器是jvm在运行时,内嵌在jvm中的一段特殊的用来加载java核心类库的C++代码

String.class 对象就是由启动类加载器加载的,启动类加载器具体加载哪些核心代码可以通过获取值为 "sun.boot.class.path" 的系统属性获得。

启动类加载器不是java原生代码编写的,所以其也不是java.lang.ClassLoader类的实例,其没有getParent方法

public static void main(String[] args) throws ClassNotFoundException {
        Class<?> aClass = Class.forName("com.mycompany.load.F");
        System.out.println(aClass.getClassLoader()); // AppClassLoader  ExtClassLoader
    }

输出

cd D:\Java\JavaProject\jvm-demo\myjvm\out\production\myjvm
D:\Java\JavaProject\jvm-demo\myjvm\out\production\myjvm>java -Xbootclasspath/a:. com.mycompany.load.Load4
bootstrap F init
null

打印 null,表示它的类加载器是 Bootstrap ClassLoader

-Xbootclasspath 表示设置 bootclasspath

其中 /a:. 表示将当前目录追加至 bootclasspath 之后

用这个办法替换核心类

java -Xbootclasspath:<new bootclasspath>

java -Xbootclasspath/a:<后追加路径>

java -Xbootclasspath/p:<前追加路径>

2.拓展类加载器

拓展类加载器用来加载jvm实现的一个拓展目录,该目录下的所有java类都由此类加载器加载。

此路径可以通过获取"java.ext.dirs"的系统属性获得。拓展类加载器就是java.lang.ClassLoader类的一个实例,其getParent方法返回的是引导类加载器(在 HotSpot虚拟机中用null表示引导类加载)

D:\Java\JavaProject\jvm-demo\myjvm\out\production\myjvm>jar -cvf my.jar com/mycompany/load/F.class
已添加清单
正在添加: com/mycompany/load/F.class(输入 = 481) (输出 = 322)(压缩了 33%)

将 jar 包拷贝到 JAVA_HOME/jre/lib/ext,重新执行代码即可

3.应用类加载器

应用类加载器又称为系统类加载器,开发者可用通过 java.lang.ClassLoader.getSystemClassLoader()方法获得此类加载器的实例,系统类加载器也因此得名。其主要负责加载程序开发者自己编写的java类

一般来说,java应用都是用此类加载器完成加载的,可以通过获取"java.class.path"的系统属性(也就是我们常说的classpath)来获取应用类加载器加载的类路径。应用类加载器是java.lang.ClassLoader类的一个实例,其getParent方法返回的是拓展类加载器

4.类的命名空间

在程序运行过程中,一个类并不是简单由其二进制名字(binary name)定义的,而是通过其二进制名和其定义加载器所确定的命名空间(run-time package)所共同确定的。

同一个二进制名的类由不同的定义加载器加载时,其返回的Class对象不是同一个,那么由不同的Class对象所创建的对象,其类型也不是相同的。

类似Test cannot be cast to Test的java.lang.ClassCastException 的奇怪错误很多情况下都是类的二进制名相同,而定义加载器不同造成的

package com.mycompany.load;
import sun.misc.Launcher;
public class Load6 {
    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
        ClassLoader classLoader = new Launcher().getClassLoader(); //1 new一个新的类加载器
        System.out.println(classLoader);
        /*
        这是因为 1处获取的应用类加载器a和jvm用来加载Load6.class对象的应用类加载器b不是同一个实例,
        那么构成这两个类的run-time package也就是不同的。所以即使它们的二进制名字相同,
        但是由a定义的Load6类所创建的对象显然不能转化为由b定义的Load6类的实例。
        这种情况下jvm就会抛出ClassCastException
        * */
        Class<?> aClass = classLoader.loadClass("com.mycompany.load.Load6");
        Load6 load6  = (Load6)aClass.newInstance(); //2
        //Exception in thread "main" java.lang.ClassCastException: com.mycompany.load.Load6 cannot be cast to com.mycompany.load.Load6
    }
}

报出异常:

java.lang.ClassCastException: com.mycompany.load.Load6 cannot be cast to com.mycompany.load.Load6

这是因为 1处获取的应用类加载器a和jvm用来加载Load6.class对象的应用类加载器b不是同一个实例, 那么构成这两个类的run-time package也就是不同的。

所以即使它们的二进制名字相同, 但是由a定义的Load6类所创建的对象显然不能转化为由b定义的Load6类的实例。 这种情况下jvm就会抛出ClassCastException

相同二进制名字的类,如果其定义加载器不同,也算是不同的两个类

二、双亲委派机制

双亲委派机制 Parent Delegation Model,又称为父级委托模型。所谓的双亲委派,就是指调用类加载器的 loadClass 方法时,查找类的规则(双亲,理解为上级更为合适,因为它们之间并没有继承关系)

1.类加载机制流程

Java编译器把Java源文件编译成.class文件,再由JVM装载.class文件到内存中,JVM装载完成后得到一个Class对象字节码。有了字节码对象,就可以实例化使用

2.类加载器加载顺序

3.双亲委派机制流程

1、加载类MyClass.class,从低层级到高层级一级一级委派,先由应用层加载器委派给扩展类加载器,再由扩展类委派给启动类加载器

(1)如果是自定义加载器挂载到应用程序类加载器

(2)应用程序类加载器将类加载请求委托给扩展类加载器

(3)扩展类加载器将类加载请求委托给启动类加载器

2、启动类加载器载入失败,再由扩展类加载器载入,扩展类加载器载入失败,最后由应用类加载器载入

(1)启动类加载器在加载路径下查找并加载Class文件,如果未找到目标Class文件,则交由扩展类加载器加载

(2)扩展类加载器在加载路径下查找并加载Class文件,如果未找到目标Class文件,则交由应用程序类加载器加载

(3)应用程序类加载器在加载路径下查找并加载Class文件,如果未找到目标Class文件,则交由自定义加载器加载

(4)在自定义加载器下查找并加载用户指定目录下的Class文件,如果在自定义加载路径下未找到目标Class文件,则抛出ClassNotFoud异常

3、如果应用类加载器也找不到那就报ClassNotFound异常了

4.源码分析

protected Class<?> loadClass(String name, boolean resolve)
            throws ClassNotFoundException {
        synchronized (getClassLoadingLock(name)) {
            // 1. 检查该类是否已经加载
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {
                        // 2. 有上级的话,委派上级 loadClass
                        c = parent.loadClass(name, false);
                    } else {
                        // 3. 如果没有上级了(ExtClassLoader),则委派
                        BootstrapClassLoader
                                c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                }
                if (c == null) {
                    long t1 = System.nanoTime();
                    // 4. 每一层找不到,调用 findClass 方法(每个类加载器自己扩展)来加载
                    c = findClass(name);
                    // 5. 记录耗时
                    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、sun.misc.Launcher$AppClassLoader //1 处, 开始查看已加载的类,如果没有

2、sun.misc.Launcher$AppClassLoader // 2 处,委派上级 sun.misc.Launcher$ExtClassLoader.loadClass()

3、sun.misc.Launcher$ExtClassLoader // 1 处,查看已加载的类,如果没有

4、sun.misc.Launcher$ExtClassLoader // 3 处,没有上级了,则委派 BootstrapClassLoader 查找

5、BootstrapClassLoader 是在 JAVA_HOME/jre/lib 下找 类,没有

6、sun.misc.Launcher$ExtClassLoader // 4 处,调用自己的 findClass 方法,是在 JAVA_HOME/jre/lib/ext 下找 类,显然没有,回到 sun.misc.Launcher$AppClassLoader 的 // 2 处

7、继续执行到 sun.misc.Launcher$AppClassLoader // 4 处,调用它自己的 findClass 方法,在 classpath 下查找,找到了

5.双亲委派机制优缺点

优点:

1、保证安全性,层级关系代表优先级,也就是所有类的加载,优先给启动类加载器,这样就保证了核心类库类

2、避免类的重复加载,如果父类加载器加载过了,子类加载器就没有必要再去加载了,确保一个类的全局唯一性

缺点:

检查类是否加载的委派过程是单向的, 这个方式虽然从结构上说比较清晰,使各个 ClassLoader 的职责非常明确, 但是同时会带来一个问题, 即顶层的ClassLoader 无法访问底层的ClassLoader 所加载的类

通常情况下, 启动类加载器中的类为系统核心类, 包括一些重要的系统接口,而在应用类加载器中, 为应用类。 按照这种模式, 应用类访问系统类自然是没有问题, 但是系统类访问应用类就会出现问题。

如在系统类中提供了一个接口, 该接口需要在应用类中得以实现, 该接口还绑定一个工厂方法, 用于创建该接口的实例, 而接口和工厂方法都在启动类加载器中。 这时, 就会出现该工厂方法无法创建由应用类加载器加载的应用实例

三、线程上下文类加载器

线程上下文类加载器就是用来解决类的双亲委托模型的缺陷

在Java中,官方为我们提供了很多SPI接口,例如JDBC、JBI、JNDI等。这类SPI接口,官方往往只会定义规范,具体的实现则是由第三方来完成的,比如JDBC,不同的数据库厂商都需自己根据JDBC接口的定义进行实现。

而这些SPI接口直接由Java核心库来提供,一般位于rt.jar包中,而第三方实现的具体代码库则一般被放在classpath的路径下。而此时问题来了:

位于rt.jar包中的SPI接口,是由Bootstrap类加载器完成加载的,而classpath路径下的SPI实现类,则是App类加载器进行加载的。

但往往在SPI接口中,会经常调用实现者的代码,所以一般会需要先去加载自己的实现类,但实现类并不在Bootstrap类加载器的加载范围内,经过前面的双亲委派机制的分析,我们已经得知:子类加载器可以将类加载请求委托给父类加载器进行加载,但这个过程是不可逆的。也就是父类加载器是不能将类加载请求委派给自己的子类加载器进行加载的,

此时就出现了这个问题:如何加载SPI接口的实现类?那就是打破双亲委派模型

SPI(Service Provider Interface):Java的SPI机制,其实就是可拔插机制。在一个系统中,往往会被分为不同的模块,比如日志模块、JDBC模块等,而每个模块一般都存在多种实现方案,如果在Java的核心库中,直接以硬编码的方式写死实现逻辑,那么如果要更换另一种实现方案,就需要修改核心库代码,这就违反了可拔插机制的原则。为了避免这样的问题出现,就需要一种动态的服务发现机制,可以在程序启动过程中,动态的检测实现者。而SPI中就提供了这么一种机制,专门为某个接口寻找服务实现的机制。如下:

当第三方实现者提供了服务接口的一种实现之后,在jar包的 META-INF/services/ 目录里同时创建一个以服务接口命名的文件,该文件就是实现该服务接口的实现类。而当外部程序装配这个模块的时候,就能通过该jar包 META-INF/services/ 里的配置文件找到具体的实现类名,并装载实例化,完成模块的注入。

基于这样一个约定就能很好的找到服务接口的实现类,而不需要在代码里制定。

同时,JDK官方也提供了一个查找服务实现者的工具类:java.util.ServiceLoader

线程上下文类加载器就是双亲委派模型的破坏者,可以在执行线程中打破双亲委派机制的加载链关系,从而使得程序可以逆向使用类加载器

1.线程上下文类加载器(Context Classloader)

线程上下文类加载器(Context Classloader)是从JDK1.2开始引入的,类Thread中的getContextClassLoader()和setContextClassLoader(ClassLoader cl)分别用来获取和设置上线文类加载器

如果没有通过setContextClassLoader(ClassLoader cl)进行设置的话,线程将继承其父线程的上下文类加载器。

Java应用运行时的初始线程的上下文类加载器是系统类加载器。在线程中运行的代码可以通过该类加载器来加载类与资源

它可以打破双亲委托机制,父ClassLoader可以使用当前线程的Thread.currentThread().getContextClassLoader()所指定的classLoader来加载类,这就可以改变父ClassLoader不能使用子ClassLoader或是其他没有直接父子关系的ClassLoader加载的类的情况,即改变了双亲委托模型

对于SPI来说,有些接口是Java核心库所提供的,而Java核心库是由启动类加载器加载的,而这些接口的实现却是来自于不同jar包(厂商提供),Java的启动类加载是不会加载其他来源的jar包,这样传统的双亲委托模型就无法满足SPI的要求。而通过给当前线程设置上下文类加载器,就可以由设置的上线文类加载器来实现与接口实现类的加载

Java提供了很多核心接口的定义,这些接口被称为SPI接口,同时为了方便加载第三方的实现类,SPI提供了一种动态的服务发现机制(约定),只要第三方在编写实现类时,在工程内新建一个META-INF/services/目录并在该目录下创建一个与服务接口名称同名的文件,那么在程序启动的时候,就会根据约定去找到所有符合规范的实现类,然后交给线程上下文类加载器进行加载处理

MySQL的Driver驱动类

在使用 JDBC 时,都需要加载 Driver 驱动,不写

Class.forName("com.mysql.jdbc.Driver")

Class.forName("com.mysql.cj.jdbc.Driver")

也可以让 com.mysql.jdbc.Driver 正确加载

在MySQL6.0之后的jar包中,遗弃了之前的com.mysql.jdbc.Driver驱动,而是使用com.mysql.cj.jdbc.Driver取而代之,因为后者不需要再自己通过Class.forName("com.mysql.jdbc.Driver")这种方式手动注册驱动,全部都可以交由给SPI机制处理

在使用 JDBC,MySQL的com.mysql.cj.jdbc.Driver的驱动类,主要就是用了Java中SPI定义的一个核心类:DriverManager,该类位于rt.jar包中,是Java中用于管理不同数据库厂商实现的驱动,同时这些各厂商实现的Driver驱动类,都继承自Java的核心类java.sql.Driver

DriverManager 的类加载器

System.out.println(DriverManager.class.getClassLoader());

打印 null,表示它的类加载器是 Bootstrap ClassLoader,会到 JAVA_HOME/jre/lib 下搜索类,但 JAVA_HOME/jre/lib 下显然没有 mysql-connector-java-xx.xx.xx.jar 包

public class DriverManager {
    // 注册驱动的集合
    private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers
        = new CopyOnWriteArrayList<>();
    // 初始化驱动
    static {
        loadInitialDrivers();
        println("JDBC DriverManager initialized");
    }
}

loadInitialDrivers() 方法

private static void loadInitialDrivers() {
        String drivers;
        try {
            drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
                public String run() {
                    return System.getProperty("jdbc.drivers");
                }
            });
        } catch (Exception ex) {
            drivers = null;
        }
        // If the driver is packaged as a Service Provider, load it.
        // Get all the drivers through the classloader
        // exposed as a java.sql.Driver.class service.
        // ServiceLoader.load() replaces the sun.misc.Providers()
        // 1、使用 ServiceLoader 机制加载驱动,即 SPI
        AccessController.doPrivileged(new PrivilegedAction<Void>() {
            public Void run() {
                ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
                Iterator<Driver> driversIterator = loadedDrivers.iterator();
                /* Load these drivers, so that they can be instantiated.
                 * It may be the case that the driver class may not be there
                 * i.e. there may be a packaged driver with the service class
                 * as implementation of java.sql.Driver but the actual class
                 * may be missing. In that case a java.util.ServiceConfigurationError
                 * will be thrown at runtime by the VM trying to locate
                 * and load the service.
                 *
                 * Adding a try catch block to catch those runtime errors
                 * if driver not available in classpath but it's
                 * packaged as service and that service is there in classpath.
                 */
                try{
                    while(driversIterator.hasNext()) {
                        driversIterator.next();
                    }
                } catch(Throwable t) {
                // Do nothing
                }
                return null;
            }
        });
        println("DriverManager.initialize: jdbc.drivers = " + drivers);
        // 2、使用 jdbc.drivers 定义的驱动名加载驱动
        if (drivers == null || drivers.equals("")) {
            return;
        }
        String[] driversList = drivers.split(":");
        println("number of Drivers:" + driversList.length);
        for (String aDriver : driversList) {
            try {
                println("DriverManager.Initialize: loading " + aDriver);
                // 这里的 ClassLoader.getSystemClassLoader() 就是应用程序类加载器
                Class.forName(aDriver, true,
                        ClassLoader.getSystemClassLoader());
            } catch (Exception ex) {
                println("DriverManager.Initialize: load failed: " + ex);
            }
        }
    }

从DriverManager中的loadInitialDrivers我们可以得知,我们即使不使用Class.forName(“com.mysql.cj.jdbc.Driver”),mysql的驱动也能被加载,这是因为后期jdk使用了ServiceLoader

2 是使用 Class.forName 完成类的加载和初始化,关联的是应用程序类加载器,因此 可以顺利完成类加载

1 就是 Service Provider Interface (SPI) 约定如下,在 jar 包的 META-INF/services 包下,以接口全限定名名为文件,文件内容是实现类名称

2.ServiceLoader

ServiceLoader 是一个简单的加载服务提供者的机制。通常服务提供者会实现服务当中所定义的接口。服务提供者可以以一种扩展的jar包的形式安装到java平台上扩展目录中,也可以添加到应用的classpath中。

1、服务提供者需要提供一个无参数的构造方法

2、服务提供者是通过在META-INF/services目录下相应的提供者配置文件,该配置文件的文件名由服务接口的包名组成。

3、提供者配置文件里面就是实现这个服务接口的类路径,每个服务提供者占一行。

4、ServiceLoader是按需加载和实例化提供者的,就是懒加载,ServiceLoader其中还包含一个服务提供者缓存,里面存放着已经加载的服务提供者。

5、ServiceLoader会返回一个iterator迭代器,会返回所有已经加载了的服务提供者

6、ServiceLoader是线程不安全的

使用ServiceLoader

ServiceLoader<接口类型> allImpls = ServiceLoader.load(接口类型.class);
Iterator<接口类型> iter = allImpls.iterator();
while(iter.hasNext()) {
    iter.next();
}

例:

ServiceLoader<Driver> loader = ServiceLoader.load(Driver.class);
Iterator<Driver> iterator = loader.iterator();
while (iterator.hasNext()){
   Driver dirver = iterator.next();
   System.out.println(dirver.getClass()+", 类加载器:"+dirver.getClass().getClassLoader());
}
System.out.println("当前线程上线文类加载器:"+Thread.currentThread().getContextClassLoader());
System.out.println("ServiceLoader类加载器:"+loader.getClass().getClassLoader());

ServiceLoader.load 方法

public static <S> ServiceLoader<S> load(Class<S> service) {
    // 获取线程上下文类加载器
    ClassLoader cl = Thread.currentThread().getContextClassLoader();
    return ServiceLoader.load(service, cl);
}

线程上下文类加载器是当前线程使用的类加载器,默认就是应用程序类加载器,它内部又是由 Class.forName 调用了线程上下文类加载器完成类加载,具体代码在 ServiceLoader 的内部类 LazyIterator 中

        private S nextService() {
            if (!hasNextService())
                throw new NoSuchElementException();
            String cn = nextName;
            nextName = null;
            Class<?> c = null;
            try {
                c = Class.forName(cn, false, loader);
            } catch (ClassNotFoundException x) {
                fail(service,
                "Provider " + cn + " not found");
            }
            if (!service.isAssignableFrom(c)) {
                fail(service,
                        "Provider " + cn + " not a subtype");
            }
            try {
                S p = service.cast(c.newInstance());
                providers.put(cn, p);
                return p;
            } catch (Throwable x) {
                fail(service,
                        "Provider " + cn + " could not be instantiated",
                        x);
            }
            throw new Error(); // This cannot happen
        }

可参考

剖析Java类加载器

Java中线程上下文类加载器的讲解

四、自定义类加载器

1、自定义类加载器场景

1、加载非 classpath 随意路径中的类文件

2、通过接口来使用实现,希望解耦时(常用在框架设计)

3、不同应用的同名类都可以加载,不冲突,常见于 tomcat 容器

2、自定义类加载器步骤

1、继承 ClassLoader 父类

2、遵从双亲委派机制,重写 findClass 方法

注:不是重写 loadClass 方法,否则不会走双亲委派机制

3、读取类文件的字节码

4、调用父类的 defineClass 方法来加载类

5、使用者调用该类加载器的 loadClass 方法

public class Load7 {
    public static void main(String[] args) throws Exception {
        MyClassLoader classLoader = new MyClassLoader();
        Class<?> c1 = classLoader.loadClass("TestServiceImpl");
        Class<?> c2 = classLoader.loadClass("TestServiceImpl");
        System.out.println(c1 == c2);//true
        MyClassLoader classLoader2 = new MyClassLoader();
        Class<?> c3 = classLoader2.loadClass("TestServiceImpl");
        //虽然相同类名,但不是同一个类加载器加载的
        System.out.println(c1 == c3);//false
        c1.newInstance();
    }
}
class MyClassLoader extends ClassLoader {
    @Override // name 就是类名称
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        String path = "D:\\myclasspath\\" + name + ".class";
        try {
            ByteArrayOutputStream os = new ByteArrayOutputStream();
            Files.copy(Paths.get(path), os);
            // 得到字节数组
            byte[] bytes = os.toByteArray();
            // byte[] -> *.class
            return defineClass(name, bytes, 0, bytes.length);
        } catch (IOException e) {
            e.printStackTrace();
            throw new ClassNotFoundException("类文件未找到", e);
        }
    }
}

到此这篇关于Java类加载器与双亲委派机制和线程上下文类加载器专项解读分析的文章就介绍到这了,更多相关Java类加载器内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Java类加载器ClassLoader的使用详解

    目录 BootstrapClassLoader ExtClassLoader AppClassLoader 类加载器的具体实现在哪里 类加载器的初始化时机 如何进行的类加载 Loader.getResource(resourceName) 调用获取资源方法的一些常见问题 BootstrapClassLoader 加载范围(根据系统参数): System.getProperty("sun.boot.class.path"); 负责加载核心类库,以我的本地的环境来展示获取的内容: D:\d

  • Java中如何自定义一个类加载器

    目录 如何自定义加载器? 示例:读取某文件的下的某class文件 类加载器的使用及自定义类加载器 如何自定义加载器? 1.创建一个自定义加载器类 继承 ClassLoader 类 2.重写 findClass 方法. 主要是实现从那个路径读取 jar包或者.class文件,将读取到的文件用字节数组来存储,然后可以使用父类的 defineClass 来转换成字节码. 如果想破坏双亲委派的话,就重写 loadClass 方法, 否则不用重写 注意: 1.ClassLoader提供的 protecte

  • Java中线程上下文类加载器超详细讲解使用

    目录 一.什么是线程上下文类加载器 1.1.重要性 1.2.使用场景 二.ServiceLoader简单介绍 三.案例 3.1.使用ServiceLoader加载mysql驱动 3.2.Class.forName加载Mysql驱动 3.2.1.com.mysql.jdbc.Driver 3.2.2.java.sql.DriverManager初始化 3.2.3.调用DriverManager的registerDriver方法 3.2.4.执行DriverManager.getConnection

  • Java类加载器ClassLoader源码层面分析讲解

    目录 Launcher 源码 AppClassLoader 源码 ExtClassLoader 源码 ClassLoader 源码 总结 最终总结一下 Launcher 源码 sun.misc.Launcher类是java 虚拟机的入口,在启动 java应用 的时候会首先创建Launcher.在初始化Launcher对象的时候会创建一个ExtClassLoader拓展程序加载器 和 AppClassLoader应用程序类加载器(这俩鬼东西好像只是加载类的路径不一样而已),然后由这俩类加载器去加载

  • Java通过自定义类加载器实现类隔离

    目录 前言 类隔离是什么 使用场景 解决方案 重写findClass 重写loadClass 总结 前言 由于微服务的快速迭代.持续集成等特性,越来越多的团队更倾向于它.但是也体现出了一些问题,比如在基础设施建设过程中,需要把通用功能下沉,把现有大而全的基础设施按领域拆分,考虑需要兼容现有生产服务,会产生不同的依赖版本,有时不注意就可以引发问题.比如本文遇到的依赖包版本冲突问题,以及如何利用类隔离技术解决的分析. 类隔离是什么 类隔离是一种通过类加载器实现加载所需类的实现方式,使得不同版本类间隔

  • java自定义类加载器如何实现类隔离

    目录 自定义类加载器 准备 通过URLClassLoader来实现[推荐] 通过继承ClassLoader实现 网上java自定义类加载器很多容易找到,但是都是加载的单个类,如果被加载的类,有引用了其他类怎么办呢?接下来看一下如何来处理这种情况 有时候一个项目中可能会引用不同版本的第三方依赖,比如笔者在升级hbase系统时,代理层就同时用到了1.X和2.X版本的hbase-client的jar包. 当时是使用的阿里的SOFAArk来实现的. 它的本质就是是哟个类加载来实现的,接下来就通过一个小例

  • Java类加载器与双亲委派机制和线程上下文类加载器专项解读分析

    目录 一.类加载器 1.启动类加载器 2.拓展类加载器 3.应用类加载器 4.类的命名空间 二.双亲委派机制 1.类加载机制流程 2.类加载器加载顺序 3.双亲委派机制流程 4.源码分析 5.双亲委派机制优缺点 三.线程上下文类加载器 1.线程上下文类加载器(Context Classloader) 2.ServiceLoader 四.自定义类加载器 一.类加载器 类加载器就是根据类的二进制名(binary name)读取java编译器编译好的字节码文件(.class文件),并且转化生成一个ja

  • 详解Java类加载器与双亲委派机制

    目录 引子 了解.class文件 类加载的过程 类加载器 与 双亲委派机制 ClassLoader 自定义类加载器 编写一个自定义的类加载器 为什么我们这边要打破双亲委派机制 自定义类加载器时,如何打破双亲委派机制 SPI机制 与 线程上下文类加载器 JDBC Tomcat SpringBoot Starter 尾语 引子 大家想必都有过平时开发springboot 项目的时候稍微改动一点代码,就得重启,就很烦 网上一般介绍 2种方式 spring-boot-devtools,或者通过JRebe

  • java中类加载与双亲委派机制详解

    目录 类加载是什么 类加载器 双亲委派机制 BootStrapClassLoader ExtClassLoader AppClassLoader 为什么使用双亲委派机制 全盘负责委托机制 自定义类加载器 打破双亲委派机制 类加载是什么 把磁盘中的java文件加载到内存中的过程叫做类加载 当我们用java命令运行某个类的main函数启动程序时,首先需要通过类加载器把主类加载到JVM. 有如下 User 类 package dc.dccmmtop; public Class User { publi

  • java 详解类加载器的双亲委派及打破双亲委派

    java 详解类加载器的双亲委派及打破双亲委派 一般的场景中使用Java默认的类加载器即可,但有时为了达到某种目的又不得不实现自己的类加载器,例如为了达到类库的互相隔离,例如为了达到热部署重加载功能.这时就需要自己定义类加载器,每个类加载器加载各自的类库资源,以此达到资源隔离效果.在对资源的加载上可以沿用双亲委派机制,也可以打破双亲委派机制. 一.沿用双亲委派机制自定义类加载器很简单,只需继承ClassLoader类并重写findClass方法即可.如下例子: ①先定义一个待加载的类Test,它

  • Java虚拟机类加载器之双亲委派机制模型案例

    1. 双亲委派模型是什么? 当某个类加载器需要加载某个.class字节码文件时,它首先把这个任务委托给它的上级类加载器,递归这个操作,如果上级的类加载器没有加载,自己才会去加载这个类. 2. 双亲委派模型的工作原理? 1.如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去执行: 2.如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器:(每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到最顶

  • Java虚拟机之双亲委派机制详解

    目录 一.原理 二.作用 三.沙箱安全机制 四.补充内容 总结 Java虚拟机对class文件采用的是按需加载的方式,也就是说当需要使用该类时才会将它的class文件加载到内存生成class对象.而且加载某个类的class文件时,Java虚拟机采用的是双亲委派模式,即把请求交由父类处理,它是一种任务委派模式. Tips:如果在工程目录的src下新建一个名为 java.lang 的包,在其中新建一个类名String的类,这个类能够正常运行吗? 程序代码如下: package java.lang;

  • JVM的类加载器和双亲委派模式你了解吗

    目录 类加载器 1.启动类加载器 2.拓展类加载器 3.应用程序类加载器 4.双亲委派模式 5.自定义类加载器 5.1.使用场景 5.2.步骤 总结 类加载器 Java虚拟机设计团队有意把类加载阶段中的“通过一个类的全限定名来获取描述该类的二进制字节流”这个动作放到Java虚拟机外部去实现,以便让应用程序自己决定如何去获取所需的类.实现这个动作的代码被称为“类加载器”(ClassLoader). 对于任意一个类,都必须由加载它的类加载器和这个类本身一起共同确立其在Java虚拟机中的唯一性,每一个

  • Tomcat打破双亲委派机制实现隔离Web应用的方法

    目录 Tomcat类加载器的层次结构 WebAppClassLoader SharedClassLoader CatalinaClassLoader CommonClassLoader Spring的加载问题 线程上下文加载器 总结 Tomcat通过自定义类加载器WebAppClassLoader打破双亲委派,即重写了JVM的类加载器ClassLoader的findClass方法和loadClass方法,以优先加载Web应用目录下的类. Tomcat负责加载我们的Servlet类.加载Servl

  • JVM的类加载过程以及双亲委派模型详解

    jvm 的主要组成部分 类加载器(ClassLoader) 运行时数据区(Runtime Data Area) 执行引擎(Execution Engine) 本地库接口(Native Interface) jvm 运行时数据区的组成 方法区: ①方法区主要用来存储已被虚拟机加载的类信息(构造器,接口定义).常量.静态变量和运行时常量池等数据. ②该区域是被线程共享的. ③方法区里有一个运行时常量池,用于存放静态编译产生的字面量和符号引用.该常量池具有动态性,也就是说常量并不一定是编译时确定,运行

随机推荐