java类加载器和tomcat的类加载

类加载器的代理模式

经典的双亲委派模式

在这里插入图片描述

1、启动类加载器BootstrapClassLoader

2、扩展类加载器Extension ClassLoader

3、系统类加载器App ClassLoader

如果一个类加载器收到了一个类加载请求,它不会自己去尝试加载这个类,而是把这个请求转交给父类加载器去完成。每一个层次的类加载器都是如此。因此所有的类加载请求都应该传递到最顶层的启动类加载器中,只有到父类加载器反馈自己无法完成这个加载请求(在它的搜索范围没有找到这个类)时,子类加载器才会尝试自己去加载。委派的好处就是避免有些类被重复加载。

线程上下文加载器

java在设计的时候,将很多功能分割出去,以方便第三方为自己开发组件,这被成为服务提供者接口(SPI)。常见的SPI有jdbc,jndi,jaxp等。这些接口存储在java核心类库中。如 JAXP 的 SPI 接口定义包含在 javax.xml.parsers包中。而实现则是由程序员引入的jar包完成的。作为核心库的一部分,这些SPI的接口应该由系统引导类加载器(Bootstrap ClassLoader)完成加载,系统类加载器显然不能取外部jar包加载它门的实现,也不能代理给App ClassLoader,因为大小辈分乱了。

线程上下文类加载器是从jdk1.2开始引入的。通过java.lang.Thread中的方法 getContextClassLoader()和 setContextClassLoader(ClassLoader cl)用来获取和设置线程的上下文类加载器。如果没有通过 setContextClassLoader(ClassLoader cl)方法进行设置的话,线程将继承其父线程的上下文类加载器。Java 应用运行的初始线程的上下文类加载器是系统类加载器App ClassLoader。在线程中运行的代码可以通过此类加载器来加载类和资源。

使用线程上下文类加载器,可以在执行线程中抛弃双亲委派加载链模式,使用线程上下文里的类加载器加载类。

具体例子

上面那一段我自己都看不懂。我们具体看看如何做。
当程序运行到

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

会执行该类的静态代码块

static {
    try {
        java.sql.DriverManager.registerDriver(new Driver());
    } catch (SQLException E) {
        throw new RuntimeException("Can't register driver!");
    }
}

DriverManager类会把这个Driver放在自己的静态变量里

private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();

如果这次是DriverManager第一次被加载,则DriverManager也会进行自己的初始化

  • 初始化JVM启动参数制定的默认JDBC实现的driver类。如果没有,则什么都不会做。
  • 通过SPI机制寻找所有Driver的SPI实现类,利用当前线程上下文加载器加载。

当是实际取连接的时候,执行下面代码:

    private static Connection getConnection(
        String url, java.util.Properties info, Class<?> caller) throws SQLException {
  
		//将classloader优先置为调用者的类加载器,如果为null则置为本线程的上下文加载器。
        ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
        //多线程同步
        synchronized(DriverManager.class) {
            if (callerCL == null) {
                callerCL = Thread.currentThread().getContextClassLoader();
            }
        }
		//轮训已经注册的driver,尝试连接
        for(DriverInfo aDriver : registeredDrivers) {
             Connection con = aDriver.driver.connect(url, info);
        }
    }

总结:
class com.mysql.jdbc.NonRegisteringDriver,class com.mysql.jdbc.Driver都是系统加载器加载的。
当需要加载器时,会使用当前类的类加载器。这很合理,因为一般核心类只会调用核心类。但是SPI破坏了这一规则,被核心类使用,但是却只能被系统加载器加载。所以,需要使用线程上下文加载器,不然核心类调用SPI时会找不到类。

具体实现

这里写图片描述
这里写图片描述
线程在加载类的时候,首先从自己的父加载器一层层向上委托(注意不是自己加载器的父类),如果没有才在自己的目录中寻找。


    // The parent class loader for delegation
    // Note: VM hardcoded the offset of this field, thus all new fields
    // must be added *after* it.
    //父加载器
    private final ClassLoader parent;

    //ClassLoader.hava
    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;
        }
    }

如果Extension ClassLoader仍然cover不住这个类加载,那么则调用native方法进行

java.lang.ClassLoader#findBootstrapClass
tomcat

有一个最基本的刚需,一个tomcat也是用java写的,上面运行的工程也是java写的。如果依赖的是一个类的不同版本,就傻了。。

一张经典的图

在这里插入图片描述

  • commonLoader:Tomcat最基本的类加载器,加载路径中的class可以被Tomcat容器本身以及各个Webapp访问;
  • catalinaLoader:Tomcat容器私有的类加载器,加载路径中的class对于Webapp不可见;
  • sharedLoader:各个Webapp共享的类加载器,加载路径中的class对于所有Webapp可见,但是对于Tomcat容器不可见;
  • WebappClassLoader:各个Webapp私有的类加载器,加载路径中的class只对当前Webapp可见;

每个web应用对应一个webApp类加载器。负责加载本应用的资源。每个webApp的classloader,会优先尝试自己加载。加载不到才会向上级加载器请求。

可以看到tomcat谁一个标准的双亲委派模型。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值