zoukankan      html  css  js  c++  java
  • 类加载器的双亲委派机制

    1、类加载器

      什么是类加载器?上篇博客我们介绍类加载过程中的第一个阶段——加载,作用是“通过一个类的全限定名来获取描述此类的二进制流”,那么这个加载过程就是由类加载器来完成的。

      从Java虚拟机的角度出发,只存在两种不同的类加载器,一种是启动类加载器(Bootstrap ClassLoader),这个类加载器使用 C++ 语言实现,是虚拟机自身的一部分;另一种是所有其它的类加载器,这些类加载器都是由Java语言实现的。但是从Java开发人员的角度来看,类加载器可以细分为如下四种:

    ①、启动类加载器(Bootstrap ClassLoader)

      负责将存放在 <JAVA_HOME>/lib 目录中的,或者被-Xbootclasspath 参数所指定的路径中的,并且是虚拟机按照文件名识别的(仅按照文件名识别,如rt.jar,名字不符合的类库即使放在lib目录中也不会被加载)类库加载到虚拟机内存中。
      
    启动类加载器无法被Java程序直接引用。

      JDK 中的源码类大都是由启动类加载器加载,比如前面说的 java.lang.String,java.util.List等,需要注意的是,启动类 main Class 也是由启动类加载器加载。

    ②、扩展类加载器(Extension ClassLoader)

       这个类加载器由 sun.misc.Launcher$ExtClassLoader 实现,负责加载<JAVA_HOME>/lib/ext 目录中的,或者被 java.ext.dirs 系统变量所指定的路径中的所有类库。

      开发者可以直接使用扩展类加载器。

    ③、应用程序类加载器(Application ClassLoader)

      由 sun.misc.Launcher$AppClassLoader 实现。由于这个类加载器是 ClassLoader.getSystemClassLoader() 方法的返回值,所以一般也称它为系统类加载器。

      它负责加载用户类路径ClassPath上所指定的类库,开发者可以直接使用这个类加载器。如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。

       通常项目中自定义的类,都会放在类路径下,由应用程序类加载器加载。

    ④、自定义类加载器(User ClassLoader)

       这是由用户自己定义的类加载器,一般情况下我们不会自定义类加载器,但有些特殊情况,比如JDBC能够通过连接各种不同的数据库就是自定义类加载器来实现的,具体用处会在后文详细介绍。

    2 类加载器的双亲委派机制

    除了根类加载器之外,其他的类加载器都需要有自己的父加载器。从JDK1.2开始,类的加载过程采用双亲委派机制,这种机制能够很好的保护java程序的安全。除了虚拟机自带的根类加载器之外,其余的类加载器都有唯一的父加载器。比如,如果需要classLoader加载一个类时,该classLoader先委托自己的父加载器先去加载这个类,若父加载器能够加载,则由父加载器加载,否则才有classLoader自己加载这个类。即每个类加载器都很懒,加载类时都先让父加载器去尝试加载,一直到根类加载器,加载不到时自己才去加载。真正加载类的加载器我们叫做启动类加载器。注意,双亲委派机制的父子关系并非面向对象程序设计中的继承关系,而是通过使用组合模式来复用父加载器代码,这种机制如下图所示:

    public static void main(String[] args) {
            ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
    
            while (classLoader!=null){
                System.out.println(classLoader);
                classLoader = classLoader.getParent();
            }
        }

    使用双亲委派机制的好处:

    1、可以避免类的重复加载,当父类加载器已经加载了该类时,就没有必要子ClassLoader再加载一次。

    2、考虑到安全因素,java核心api中定义类型不会被随意替换,假设通过网络传递一个名为java.lang.Object的类,通过双亲委托模式传递到启动类加载器,而启动类加载器在核心Java API发现这个名字的类,发现该类已被加载,并不会重新加载网络传递的过来的java.lang.Object,而直接返回已加载过的Object.class,这样便可以防止核心API库被随意篡改。

    package java.lang;
    
    /**
     * @author WGR
     * @create 2020/4/26 -- 20:58
     */
    public class MyObject {
        //加载该类
        public static void main(String[] args) {
            Class clazz = MyObject.class;
            System.out.println(clazz.getClassLoader());
        }
    }

    因为java.lang包属于核心包,只能由根类加载器进行加载,而根据类加载的双亲委派机制,根类加载器是加载不到这个MyObject类的(自定义的),所以只能由AppClassLoader进行加载,而这又不是允许的,所以会报出“Prohibited package name: java.lang”(禁止的包名)错误。

    ClassLoader

    所有的类加载器(除了根类加载器)都必须继承java.lang.ClassLoader。它是一个抽象类,主要的方法如下:

    loadClass

    在ClassLoader的源码中,有一个方法loadClass(String name,boolean resolve),这里就是双亲委托模式的代码实现。从源码中我们可以观察到它的执行顺序。需要注意的是,只有父类加载器加载不到类时,会调用findClass方法进行类的查找,所以,在定义自己的类加载器时,不要覆盖掉该方法,而应该覆盖掉findClass方法。

    //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;
            }
        }

    findClass

    在自定义类加载器时,一般我们需要覆盖这个方法,且ClassLoader中给出了一个默认的错误实现。

    protected Class<?> findClass(String name) throws ClassNotFoundException {
        throw new ClassNotFoundException(name);
    }

    defineClass

    该方法的签名如下。用来将byte字节解析成虚拟机能够识别的Class对象。defineClass()方法通常与findClass()方法一起使用。在自定义类加载器时,会直接覆盖ClassLoader的findClass()方法获取要加载类的字节码,然后调用defineClass()方法生成Class对象。

    protected final Class<?> defineClass(String name,byte[] b,int off,int len)
                                  throws ClassFormatError

    resolveClass

    连接指定的类。类加载器可以使用此方法来连接类。

    URLClassLoader

    在java.net包中,JDK提供了一个更加易用的类加载器URLClassLoader,它扩展了ClassLoader,能够从本地或者网络上指定的位置加载类。我们可以使用该类作为自定义的类加载器使用。

    构造方法:

    public URLClassLoader(URL[] urls):指定要加载的类所在的URL地址,父类加载器默认为系统类加载器。

    public URLClassLoader(URL[] urls, ClassLoader parent):指定要加载的类所在的URL地址,并指定父类加载器。

    案例1:加载磁盘上的类

     //加载该类
        public static void main(String[] args) throws Exception {
            File file = new File("d:/");
            URI uri = file.toURI();
            URL url = uri.toURL();
            URLClassLoader classLoader = new URLClassLoader(new URL[]{url});
            System.out.println(classLoader.getParent());
            Class aClass = classLoader.loadClass("com.topcheer.Demo");
            Object obj = aClass.newInstance();
        }

     案例2:加载网络上的类

        public static void main(String[] args) throws Exception{
            URL url = new URL("http://localhost:8080/examples/");
            URLClassLoader classLoader = new URLClassLoader(new URL[]{url});
            System.out.println(classLoader.getParent());
            Class aClass = classLoader.loadClass("com.topcheer.Demo");
            aClass.newInstance();
        }

  • 相关阅读:
    react redux
    react 路由 react-router@3.2.1
    react 生命周期
    重载 UINavigationController 设置左侧返回按钮的文字为图片
    使用 SQLiteManager 操作 sqlite3 数据库
    使用 DES 算法对数据加密
    使用 Reachability 获取网络状态
    NSPredicate 的使用(持续更新)
    使用开源库 SDWebImage 异步下载缓存图片(持续更新)
    Grand Central Dispatch (GCD)
  • 原文地址:https://www.cnblogs.com/dalianpai/p/12782379.html
Copyright © 2011-2022 走看看