JVM基础之类加载器

简介

本次介绍类加载器,内容较多,包含双亲委派及代码分析,spi等,希望大家学得开心,学得愉快

配套视频讲解:www.bilibili.com/video/BV173… (更新可能会迟到但不会缺席)

初步认识

类加载器作用

我们知道了,java代码运行要先编译成class字节码,再被加载到jvm中,那是被什么”东西“加载的呢? 这个”东西“就是类加载器

image.png

类加载器分类

jdk提供的类加载器有

  • 启动类加载器(Bootstrap ClassLoader)
  • 扩展类加载器(Extension ClassLoader)
  • 应用类加载器(Application ClassLoader)

类加载器的分工

分工:其实就是每个类加载器只加载某个路径下的内容

应用程序类加载器:加载的是classpath底下的所有class文件。详细见下图

image.png

扩展类加载器: 加载的是jre/lib/ext/

image.png

启动类加载器 加载的是/jre/lib/rt.jar

image.png

image.png

双亲委派

聊到类加载器,就不得不提“双亲委派机制”,双亲委派这个词,听起来有些晦涩难懂,其实很简单。可以理解为,类加载器之间的协作模式,结合图理解以下的文字。

如果现在需要加载某个类(例如代码中写 new Test(),那就要去加载Test类了)

  1. 应用程序到自己的缓存里找,如果找到了,直接返回(结束),如果没找到就让扩展类加载器找

image.png

(假设现在应用程序类加载器没找到,所以就到了扩展类加载器)

  1. 扩展类加载器同样会到自己的缓存里找,如果能找到直接返回,找不到就让启动类加载器找

image.png (假设现在扩展类加载器没找到,所以就到了启动类加载器)

  1. 启动类加载器同样会在缓存里找,找到了就返回,找不到,则到自己负责的路径下找(/jre/li/rt.jar),找到了就返回,找不到就又把”锅“甩给扩展类加载器

image.png

4.扩展类加载器同样会到自己负责的路径下找(jre/lib/ext/),找不到把锅继续甩回给应用程序类加载器

image.png

  1. 应用类加载器会在classpath下找,如果找不到就抛出ClassNotFound异常

image.png

贴士: 类加载器中的父子关系是逻辑上的父子,而不是代码里的extend

为什么要双亲委派

安全,安全,还是安全。

代码千万行,安全第一条。

试想如果你自己定义了一个java.lang.String的类,放到了classpath下,如果直接被应用类加载器加载到内存就是件很危险的事,因为这意味着你可以通过这种方式代替jdk原本的String类。然后从中做点手脚比如,(往你的银行卡转钱)

image.png

看看源码

我们通过窥探源码到方式进一步,对双亲委派机制做一个深入了解。接下来我把关键位置代码截出来做解释。请结合上半部分的理论内容理解

c++代码

因为java运行在jvm上,为了接近问题的源头所以看一点c++部分的代码

loandMainCLass:看名字就猜到了,它就是加载我们java代码中main方法所在那个的类 (java.c文件)

image.png

进入loandMainCLass后,可以看到调用了GetStaticMethodID方法且传了checkAndLoadMain参数,这是一个分水岭,从这之后就进入到java代码中了 image.png

多说一句: GetStaticMethodID获取静态函数main的id

java部分

scloader的类型就是AppClassLoader(应用类加载器)

image.png

我们先看一眼AppClassLoader

它继承了UrlClassLoader image.png

UrlClassLoader继承了SecureClassLoader image.png

SecureClassLoader继承了ClassLoader image.png

梳理一下

image.png

回到刚刚的loadClass,AppClassLoader,URLClassLoader,SecureClassLoader,都没对loadClass重写,所以此时调用的是 ClassLoader中的loadClass

我们看一眼loadClass

image.png

调用了LoadClass(name,false),这个方法被AppClassLoader重写过,所以调的是AppClassLoader重点LoadClass

image.png 这个方法最终又调用了父类的LoadClass,也就是ClassLoader。

下面是ClassLoader中的LoadClass方法,我个人认为下面这段代码写的特别的精炼。我们一起来膜拜下大神的思维

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;
    }
}
复制代码

findLoadedClass:查找被加载过的类,因为当前没有加载过,所以这里一定是null

image.png

让“父”加载器加载,可以看到父加载器的类型就是ExtClassLoader(扩展类加载器)

image.png

我们看一眼ExtClassLoader的代码

image.png

image.png

它的继承关系跟AppClassLoader很像

image.png

回到parent.loadClass,因为ExtClassLoader也继承的是ClassLoader,所以最终又执行到了这个地方,但注意此时的this是ExtClassLoader

image.png

image.png

很显然,ExtClassLoader的findLoadedClass也是null,所以就会走到findBootstrapClassOrNull

image.png

贴士: ExtClassLoader的父加载器是BootStrap写在c++中,从下图中可以看到findBootstrapClassOrNull最终调用了native方法

image.png

此时要加载的类是我们自己写的,应由应用类加载器进行加载,所以findBootstrapClassOrNull会抛出ClassNotFoundException的异常

image.png

异常捕获后,ExtClassloader尝试在自己负责的目录下加载类,当然也加载不到最终也会抛出异常

image.png

应用类加载器捕获异常后同样会尝试自己加载类(从classpath下),因为我们的类在classpath下存在,所以能够被加载,否则抛出ClassNotFoundException异常

image.png

到这里,双亲委派的代码基本就差不多了。我们再看一眼 parent是怎么赋值的

回到最初的起点

image.png

看一眼scloader定义的地方,

image.png

再看getSystemClassLoader

image.png

里面有行sun.misc.Launcher.getLauncher(); image.png

看构造函数

image.png

ExtClassLoader与AppClassLoader就在这创建了

image.png

从这就可以看到ExtClassLoader的parent是null image.png

AppClassLoader的parent是ExtClassLoader

image.png

image.png

到这重要环节的代码就差不多了

spi

绕开双亲委派

聊SPI之前,我们了解一个机制,当我们的类A用到了另一个类B,假设这个类B之前没被加载过,那么此时就要加载类B,加载类B就要用到类加载器,那么用哪一个加载器呢?其实就是用类A的加载器。

image.png

那么这就会产生一个问题(以jdbc举例)

driver在rt.jar下由启动类加载,但各个厂商提供的数据库驱动实现在classpath下,启动类加载器加载不到

image.png

解决这个问题就可以用spi来绕过双亲委派,我们看一眼源码,其实就是获取了线程上下文类加载器

image.png

image.png

spi演示

简单的演示下spi怎么用,不做具体解释了(因为不是这篇文章的重点)

随便写一个接口

image.png

随便写一个实现类

image.png

在这个目录下新建文件,名字是接口的全限定类名,内容是实现类的全限定类名。

image.png

随便测试一下

image.png

本次就到这,再见~。

猜你喜欢

转载自juejin.im/post/7042303715230892039