java classLoader双亲委派源码分析

一个java程序被执行,编译器先将.java文件编译成class文件,然后classloader将class文件加载到jvm内存中来执行应用程序。classloader起到了至关重要的作用,下面我们来看下classloader的分类:

系统类加载器

  • Bootstrap ClassLoader
    用C/C++代码实现的加载器,用于加载Java虚拟机运行时所需要的系统类,如java.lang.、java.uti.等这些系统类,它们默认在$JAVA_HOME/jre/lib目录中,也可以通过启动Java虚拟机时指定-Xbootclasspath选项,来改变Bootstrap ClassLoader的加载目录。
    Java虚拟机的启动就是通过 Bootstrap ClassLoader创建一个初始类来完成的。由于Bootstrap ClassLoader是使用C/C++语言实现的, 所以该加载器不能被Java代码访问到。需要注意的是Bootstrap ClassLoader并不继承java.lang.ClassLoader。
  • Extensions ClassLoader
    用于加载 Java 的拓展类 ,拓展类的jar包一般会放在$JAVA_HOME/jre/lib/ext目录下,用来提供除了系统类之外的额外功能。也可以通过-Djava.ext.dirs选项添加和修改Extensions ClassLoader加载的路径。
  • App ClassLoader
    负责加载当前应用程序Classpath目录下的所有jar和Class文件。也可以加载通过-Djava.class.path选项所指定的目录下的jar和Class文件。

自定义类加载器

就是我们自己写一个类继承ClassLoader,然后重写findClass方法加载指定目录下的class文件

classLoader继承关系

我们知道系统所提供的类加载器有3种类型,但是系统提供的ClassLoader相关类却不只3个。另外,AppClassLoader的父类加载器为ExtClassLoader,并不代表AppClassLoader继承自ExtClassLoader,ClassLoader的继承关系如下所示。


image.png

可以看到上图中共有5个ClassLoader相关类,下面简单对它们进行介绍:

  • ClassLoader是一个抽象类,其中定义了ClassLoader的主要功能。
  • SecureClassLoader继承了抽象类ClassLoader,但SecureClassLoader并不是ClassLoader的实现类,而是拓展了ClassLoader类加入了权限方面的功能,加强了ClassLoader的安全性。
  • URLClassLoader继承自SecureClassLoader,用来通过URl路径从jar文件和文件夹中加载类和资源。
  • ExtClassLoader和AppClassLoader都继承自URLClassLoader,它们都是Launcher 的内部类,Launcher 是Java虚拟机的入口应用,ExtClassLoader和AppClassLoader都是在Launcher中进行初始化的。

双亲委派机制

类加载器查找Class所采用的是双亲委托模式,所谓双亲委托模式就是首先判断该Class是否已经加载,如果没有则不是自身去查找而是委托给父加载器进行查找,这样依次的进行递归,直到委托到最顶层的Bootstrap ClassLoader,如果Bootstrap ClassLoader找到了该Class,就会直接返回,如果没找到,则继续依次向下查找,如果还没找到则最后会交由自身去查找。

image.png

这样讲可能会有些抽象,来直接看源码:

    protected Class loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
            // First, check if the class has already been loaded
            Class c = findLoadedClass(name);//第一步
            if (c == null) {
                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.
                    c = findClass(name);
                }
            }
            return c;
    }

    protected final Class findLoadedClass(String name) {
        ClassLoader loader;
        if (this == BootClassLoader.getInstance())
            loader = null;
        else
            loader = this;
        return VMClassLoader.findLoadedClass(loader, name);
    }
    1. findLoadedClass 在当前classloader中查找是否加载过该class,如果没加载过就交给父加载器进行加载
    1. parent.loadClass(name, false),父加载器存在的话就交给父加载器进行加载,不存在的就直接交由系统跟类加载器家在。同样父加载器也是在当前的classloader中查找是否加载过该class,如果没加载过就交给父加载器进行加载。不断重复第一步,递归调用。
    1. 一直委托到Bootstrap ClassLoader,如果Bootstrap ClassLoader在缓存中还没有查找到Class文件,则在自己的规定路径$JAVA_HOME/jre/libr中或者-Xbootclasspath选项指定路径的jar包中进行查找,如果找到则返回该Class,如果没有则交给子加载器Extensions ClassLoader。
    1. Extensions ClassLoader查找$JAVA_HOME/jre/lib/ext目录下或者-Djava.ext.dirs选项指定目录下的jar包,如果找到就返回,找不到则交给App ClassLoader。
    1. App ClassLoade查找Classpath目录下或者-Djava.class.path选项所指定的目录下的jar包和Class文件,如果找到就返回,找不到交给我们自定义的类加载器,如果还找不到则抛出异常

两个过程,先从下到上查询是否有类加载器加载过该class,然后从上到下询问类加载器是否可以加载该class

双亲委托模式的好处

采取双亲委托模式主要有两点好处:

    1. 避免重复加载,如果已经加载过一次Class,就不需要再次加载,而是先从缓存中直接读取。
    1. 更加安全,如果不使用双亲委托模式,就可以自定义一个String类来替代系统的String类,这显然会造成安全隐患,采用双亲委托模式会使得系统的String类在Java虚拟机启动时就被加载,也就无法自定义String类来替代系统的String类,除非我们修改
      类加载器搜索类的默认算法。还有一点,只有两个类名一致并且被同一个类加载器加载的类,Java虚拟机才会认为它们是同一个类,想要骗过Java虚拟机显然不会那么容易

你可能感兴趣的:(java classLoader双亲委派源码分析)