zoukankan      html  css  js  c++  java
  • Flink 源码(九):阅读 Flink 源码前必会的知识(四)SPI 和 ClassLoader(一)ClassLoader

    来源:https://mp.weixin.qq.com/s/PtmlneRo6AG4Fyb8y-Bvrw

    一 简介

    二、ClassLoader 类加载器

    1、Java 中的类加载器以及双亲委派机制

    Java 中的类加载器,是 Java 运行时环境的一部分,负责动态加载 Java 类到 Java 虚拟机的内存中。

    有了类加载器,Java 运行系统不需要知道文件与文件系统。

    那么类加载器,什么类都加载吗?加载的规则是什么?

    Java 中的类加载器有四种,分别是:

    • BootstrapClassLoader,顶级类加载器,加载JVM自身需要的类;

    • ExtClassLoader,他负责加载扩展类,如 jre/lib/ext 或 java.ext.dirs 目录下的类;

    • AppClassLoader,他负责加载应用类,所有 classpath 目录下的类都可以被这个类加载器加载;

    • 自定义类加载器,如果你要实现自己的类加载器,他的父类加载器都是AppClassLoader。

      类加载器采用了双亲委派模式,其工作原理是,如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行。

      如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器。

      如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式。

    双亲委派模式的好处是什么?

      第一,Java 类随着它的类加载器一起具备了一种带有优先级的层次关系,通过这种层次关系可以避免类的重复加载,当父类加载器已经加载过一次时,没有必要子类再去加载一次。

      第二,考虑到安全因素,Java 核心 Api 类不会被随意替换,核心类永远是被上层的类加载器加载。如果我们自己定义了一个 java.lang.String 类,它会优先委派给 BootStrapClassLoader 去加载,加载完了就直接返回了。

      如果我们定义了一个 java.lang.ExtString,能被加载吗?答案也是不能的,因为 java.lang 包是有权限控制的,自定义了这个包,会报一个错如下:

    java.lang.SecurityException: Prohibited package name: java.lang

    2、双亲委派机制源码浅析

    Java 程序的入口就是 sun.misc.Launcher 类,我们可以从这个类开始看起。

    下面是这个类的一些重要的属性,写在注释里了。

    public class Launcher {
        private static URLStreamHandlerFactory factory = new Launcher.Factory();
        // static launchcher 实例
        private static Launcher launcher = new Launcher();
        // bootclassPath ,就是 BootStrapClassLoader 加载的系统资源
        private static String bootClassPath = System.getProperty("sun.boot.class.path");
        // 在 Launcher 构造方法中,会初始化 AppClassLoader,把它作为全局实例保存起来
        private ClassLoader loader;
        private static URLStreamHandler fileHandler;
        ......
    }

    这个类加载的时候,就会初始化 Launcher 实例,我们看一下无参构造方法。

     public Launcher() {
            Launcher.ExtClassLoader var1;
            try {
                // 获得 ExtClassLoader
                var1 = Launcher.ExtClassLoader.getExtClassLoader();
            } catch (IOException var10) {
                throw new InternalError("Could not create extension class loader", var10);
            }
    
            try {
                // 获得 AppClassLoader,并赋值到全局属性中
                this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
            } catch (IOException var9) {
                throw new InternalError("Could not create application class loader", var9);
            }
      
            // 把 AppClassLoader 的实例赋值到当前上下文的 ClassLoader 中,和当前线程绑定
            Thread.currentThread().setContextClassLoader(this.loader);
           // ...... 省略无关代码
    
        }

    可以看到,先获得一个 ExtClassLoader ,再把 ExtClassLoader 作为父类加载器,传给 AppClassLoader。最终会调用这个方法,把 ExtClassLoader 传给 parent 参数,作为父类加载器。

    而在初始化 ExtClassLoader 的时候,没有传参:

    Launcher.ExtClassLoader var1;
            try {
                var1 = Launcher.ExtClassLoader.getExtClassLoader();
            } catch (IOException var10) {
                throw new InternalError("Could not create extension class loader", var10);
            }

    而最终,给 ExtClassLoader 的 parent 传的参数是 null。可以先记住这个属性,下面在讲 ClassLoader 源码时会用到这个 parent 属性。

     然后 Launcher 源码里面还有四个系统属性,值得我们运行一下看看,如下图

    从上面的运行结果中,我们也可以轻易看到不同的类加载器,是从不同的路径下加载不同的资源。而即便我们只是写一个 Hello World,类加载器也会在后面默默给我们加载这么多类。

     

    看完了 Launcher 类的代码,我们再来看 java.lang.ClassLoader 的代码,真正的双亲委派机制的源码是在这个类的 loaderClass 方法中。

       protected Class<?> loadClass(String name, boolean resolve)
            throws ClassNotFoundException
        {
            synchronized (getClassLoadingLock(name)) {
                // 首先,检查这个类是否已经被加载了,最终实现是一个 native 本地实现
                Class<?> c = findLoadedClass(name);
                // 如果还没有被加载,则开始架子啊
                if (c == null) {
                    long t0 = System.nanoTime();
                    try {
                        // 首先如果父加载器不为空,则使用父类加载器加载。Launcher 类里提到的 parent 就在这里使用的。
                        if (parent != null) {
                            c = parent.loadClass(name, false);
                        } else {
                            // 如果父加载器为空(比如 ExtClassLoader),就使用 BootStrapClassloader 来加载
                            c = findBootstrapClassOrNull(name);
                        }
                    } catch (ClassNotFoundException e) {
                    }
        
                    // 如果还没有找到,则使用 findClass 类来加载。也就是说如果我们自定义类加载器,就重写这个方法
                    if (c == null) {
                        long t1 = System.nanoTime();
                        c = findClass(name);
    
                        sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                        sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                        sun.misc.PerfCounter.getFindClasses().increment();
                    }
                }
                if (resolve) {
                    resolveClass(c);
                }
                return c;
            }
        }

    这段代码还是比较清晰的,加载类的时候,首先判断类是不是已经被加载过了,如果没有被加载过,则看自己的父类加载器是不是为空。如果不为空,则使用父类加载器加载;如果父类加载器为空,则使用 BootStrapClassLoader 加载。

    最后,如果还是没有加载到,则使用 findClass 来加载类。

    类加载器的基本原理就分析到这里,下面我们再来分析一个 Java 中有趣的概念,SPI。

  • 相关阅读:
    用python3实现AES/CBC/PKCS5padding算法加解密
    python之逆向某贷款app破解sign参数
    用Python实现RSA签名和验签
    python3 RSA 长字符串分段加密解密
    PyCharm 字体大小颜色常用功能设置
    pycharm2019.3/pycharm2020.2 专业版 安装教程永久激活
    Android 四大组件和Intent
    Linux的查找命令
    Linux(centos)系统各个目录的作用详解
    linux ls文件颜色和底色设置
  • 原文地址:https://www.cnblogs.com/qiu-hua/p/14489239.html
Copyright © 2011-2022 走看看