ClassLoader总结

作者: 打伞的Fish | 来源:发表于2018-06-10 21:56 被阅读15次

    通读这篇文章你会知道如何回答以下问题:

    • Java自带的三大加载器加载的jar位置都是在哪里?
    • 三大加载器之间的关系是怎么样的? 在代码中是如何体现的?
    • 双亲委派模型是什? 代码中如何体现这种模式的应用? 这种模式的不足是什么?
    • 上下文加载器存在的作用是什么?应用的场景有哪些?

    java三大加载器加载的jar位置

    知道每个加载器加载什么位置的jar,这对后面分析委托机制会起到作用。
    Java语言自带的有三个类加载器

    1. Bootstrap CLassloder

    2. Extention ClassLoader

    3. AppClassLoader
      以下是输出各个加载器的加载jar的位置,这些路径可以通过虚拟机参数进行修改。

      public static void testClassLoader() {
       System.out.println("BootstrapClassLoader:");
       String property = System.getProperty("sun.boot.class.path");
       Arrays.stream(property.split(";")).forEach(System.out::println);
      
       System.out.println("ExtClassLoader :");
       property = System.getProperty("java.ext.dirs");
       Arrays.stream(property.split(";")).forEach(System.out::println);
      
       System.out.println("AppClassLoader :");
       property = System.getProperty("java.class.path");
       Arrays.stream(property.split(";")).forEach(System.out::println);    }
      
    • Bootstrap CLassloder 加载位置如下:
      JDK\jdk1.8\jre\lib\resources.jar
      JDK\jdk1.8\jre\lib\rt.jar
      JDK\jdk1.8\jre\lib\sunrsasign.jar
      JDK\jdk1.8\jre\lib\jsse.jar
      JDK\jdk1.8\jre\lib\jce.jar
      JDK\jdk1.8\jre\lib\charsets.jar
      JDK\jdk1.8\jre\lib\jfr.jar
      JDK\jdk1.8\jre\classes
    • Extention ClassLoader加载位置如下:
      C:\Program Files\Java\jre1.8.0_91\lib\ext;C:\Windows\Sun\Java\lib\ext;
    • AppClassLoader 加载位置如下:
      就是你项目配置的jar路径以及工程生成的classes的位置,如maven会是 target\classes,或者bin目录;
    • 这些路径的可以从sun.misc.Launcher类得知;

    加载器之间的关系

    • 如果类是在boostrapClassLoder下加载,无法获取其加载器
      从报错的情况,因为bootstrapclassloader有加载过rt.jar,这个从前面可以看出来,Integer类是rt.jar里面的类,所以Integer是由bootstrapclassloader进行加载的没错,那为啥是空指针呢?

      1-1
      来一张extclassloader的继承图,AppClassLoader也一样的继承关系
      图1
      这个空指针我是这么理解的:凡是有boostraploader进行加载的类,都是获取不到此类的加载器,因为Bootstrap ClassLoader是由C/C++编写的,它本身是虚拟机的一部分,所以它并不是一个JAVA类,也就是无法在java代码中获取它的引用;
    • ExtClassLoadder的父加载器是null
      接下来从源码角度来看下,图2输出的原因(即extClassLoader的父记载器为啥null)

      图2
      关键从parent方法入手,可以发现在图一的继承关系中找到parent方法是在ClassLoader中如图3,从idea点击过去也可以找到,从方法来看parent是final成员变量,所以找到构造方法,就知道如何它是如何初始化了
      图3
      从下面的代码可以看出parent初始化有两种方式:
      一种是指定ClassLoader;
      第二种如果没有指定则通过initSystemClassLoader方法进行初始化,这个方法获取classLoader方式是从Launcher类的getClassLoader方法获取的;
       protected ClassLoader(ClassLoader parent) {
       this(checkCreateClassLoader(), parent);
       }   
        protected ClassLoader() {
       this(checkCreateClassLoader(), getSystemClassLoader());
       }
       public static ClassLoader getSystemClassLoader() {
       initSystemClassLoader();
       if (scl == null) {
           return null;
       }
       SecurityManager sm = System.getSecurityManager();
       if (sm != null) {
           checkClassLoaderPermission(scl, Reflection.getCallerClass());
       }
              return scl;
       }
       private static synchronized void initSystemClassLoader() {
       if (!sclSet) {
           if (scl != null)
               throw new IllegalStateException("recursive invocation");
           sun.misc.Launcher l = sun.misc.Launcher.getLauncher();
           if (l != null) {
               Throwable oops = null;
               scl = l.getClassLoader();
               try {
                   scl = AccessController.doPrivileged(
                       new SystemClassLoaderAction(scl));
               } catch (PrivilegedActionException pae) {
                   oops = pae.getCause();
                   if (oops instanceof InvocationTargetException) {
                       oops = oops.getCause();
                   }
               }
               if (oops != null) {
                   if (oops instanceof Error) {
                       throw (Error) oops;
                   } else {
                       // wrap the exception
                       throw new Error(oops);
                   }
               }
           }
           sclSet = true;
       }   
        }
      

    Launcher类的classLoader方法初始化如下:
    从代码可知Launcher.getClassLoader就是获取appclassLoader,意味着一个类的父加载器如果没有指定,则是默认就是AppClassLoader。

        public Launcher() {
         Launcher.ExtClassLoader var1;
         try {
            var1 = Launcher.ExtClassLoader.getExtClassLoader();
         } catch (IOException var10) {
            throw new InternalError("Could not create extension class loader", var10);
        }
     try {
            this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
        } catch (IOException var9) {
            throw new InternalError("Could not create application class loader", var9);
        }
    
        Thread.currentThread().setContextClassLoader(this.loader);
        String var2 = System.getProperty("java.security.manager");
        if(var2 != null) {
            SecurityManager var3 = null;
            if(!"".equals(var2) && !"default".equals(var2)) {
                try {
                    var3 = (SecurityManager)this.loader.loadClass(var2).newInstance();
                } catch (IllegalAccessException var5) {
                    ;
                } catch (InstantiationException var6) {
                    ;
                } catch (ClassNotFoundException var7) {
                    ;
                } catch (ClassCastException var8) {
                    ;
                }
            } else {
                var3 = new SecurityManager();
            }
            if(var3 == null) {
                throw new InternalError("Could not create SecurityManager: " + var2);
            }
            System.setSecurityManager(var3);
        }
    }
    public ClassLoader getClassLoader() {
        return this.loader;
    }
    

    但是这个还是无法解释extClassLoader的父加载器是null,请看Laucher类中ExtClassLoader类的构造法方方法,指定的父加载器就是null,所以从parent变量的获取追踪到extClassloader是由于指定null父加载器,所以导致extClassLoader获取父加载器是null,
    而AppClassLoader获取父加载器是extClassLoader,是因为指定了extClassLoader为自己的父加载器;

    extclassLoader构造方法
    appclassLoader构造方法
    • 从源码角度理解双亲委托机制以及boostrapClassLoader为啥可以作为extClassLoader的父加载器
      双亲委托是什么,看过博主的文章就知道了,一句话概括

    委托是从下向上,然后具体查找过程却是自上至下

    从ClassLoader的loadClass可以明白双亲委托机制过程,同时知道如果自定义的ClassLoader是覆盖findClass,而不是loadClass,采用这种方式进行加载可以避免java核心api中定义的类型被自定义的加载器加载,从而出现多个


    ClassLoadere-loadClass
    • 自定义加载器,自己也写个,跟博主一样,然后调试了一遍,对加载过程再熟悉一遍

      public class MyClassLoader extends  ClassLoader{
      private String filePath;
      
      MyClassLoader(String filePath) {
        this.filePath = filePath;
      }
      
      @Override
       public Class<?> findClass(String name) throws ClassNotFoundException {
        int i = name.lastIndexOf(".") +1;
        String s = name.substring(i) + ".class";
        File file = new File(filePath, s);
        try {
            FileInputStream is = new FileInputStream(file);
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            int len = 0;
            try {
                 while ((len = is.read()) != -1) {
                    bos.write(len);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            byte[] data = bos.toByteArray();
            is.close();
            bos.close();
            return defineClass(name,data,0,data.length);
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return super.findClass(name);
      }
      
      public static void main(String[] args) throws Exception{
        MyClassLoader classLoader = new MyClassLoader("D:\\");
        Class<?> aClass = classLoader.loadClass("com.example.Product");
        if (aClass != null) {
            Object o = aClass.newInstance();
            Method ok = aClass.getDeclaredMethod("ok");
            ok.invoke(o);
        }
      }
      }
      
    • 打破双亲委派模型------引出上下文类加载器
      双亲委派模型的加载方式是从底层加载器到顶层加载器进行加载,但是如果顶层加载器要加载来自底层加载器的类时,此时就传统加载模式就无法完成了。
      这种情况会出现在SPI使用中,引用一段话来说明SPI加载的情况

    SPI机制简介
    SPI的全名为Service Provider Interface,主要是应用于厂商自定义组件或插件中。在java.util.ServiceLoader的文档里有比较详细的介绍。简单的总结下java SPI机制的思想:我们系统里抽象的各个模块,往往有很多不同的实现方案,比如日志模块、xml解析模块、jdbc模块等方案。面向的对象的设计里,我们一般推荐模块之间基于接口编程,模块之间不对实现类进行硬编码。一旦代码里涉及具体的实现类,就违反了可拔插的原则,如果需要替换一种实现,就需要修改代码。为了实现在模块装配的时候能不在程序里动态指明,这就需要一种服务发现机制。 Java SPI就是提供这样的一个机制:为某个接口寻找服务实现的机制。有点类似IOC的思想,就是将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要。
    SPI具体约定
    Java SPI的具体约定为:当服务的提供者提供了服务接口的一种实现之后,在jar包的META-INF/services/目录里同时创建一个以服务接口命名的文件。该文件里就是实现该服务接口的具体实现类。而当外部程序装配这个模块的时候,就能通过该jar包META-INF/services/里的配置文件找到具体的实现类名,并装载实例化,完成模块的注入。基于这样一个约定就能很好的找到服务接口的实现类,而不需要再代码里制定。jdk提供服务实现查找的一个工具类:java.util.ServiceLoader

    以下面这段代码来说明双亲委派模型的 逆向加载

        String url = "jdbc:mysql://localhost:3306/mysql";
          //通过java库获取数据库连接
        Connection conn = java.sql.DriverManager.getConnection(url, "root", "123456");
    

    首先调用静态方法必将导致调用者会进行初始化,所以DriverMananger类将初始化,如果类中有静态代码块必将执行,下面是DriverManager类的代码:


    静态代码k loadInitialDrivers方法

    因为这句Class.forName(DriverName, false, loader)代码所在的类在java.util.ServiceLoader类中,而ServiceLoader.class又加载在BootrapLoader中,因此传给 forName 的 loader 必然不是BootrapLoader,那会是什么加载器呢,就是上下文加载,这个可以从load方法中看出:

    load

    所以这边就可以看出要通过顶层加载器去加载底层加载器的类时,通过上下文加载器实现;

    相关文章

      网友评论

        本文标题:ClassLoader总结

        本文链接:https://www.haomeiwen.com/subject/mlwpjftx.html