zoukankan      html  css  js  c++  java
  • 聊聊 ClassLoader 是如何查找资源的

    ClassLoader作用

       classloader这个写业务代码的童鞋们,应该很少用到,但是写框架的应该很熟悉。这个类负责Java底层的类的加载和查找,简单滴说Java 的所有类都是由它负责将class文件加载到JVM

        这篇文章主要是讲解,ClassLoader是如何查找和加载类的

    常用的ClassLoader

       ①:BootStrapClassloader  负责加载jre下面的核心类

       ②:ExtClassLoader      负责加载jre/ext下面的类

       ③:AppClassloader      负责加载classpath下面的类或者jar,这个classpath很广,包括(我们平常能接触到也就是这个AppClassload)

                         ①:jre/lib下面的部分包

                         ②:web项目的classpath路径下的所有的类和资源

                         ③:web项目依赖的第三方jar包

       ④:自定义classloader

       如果有兴趣的话,你们可以分别打印出每个classloader管理的资源,代码如下

     
     public static  void print(){
          URL[] bootStrapUrls = Launcher.getBootstrapClassPath().getURLs();
          System.out.println("-----------boot-----------------");
          for(URL url : bootStrapUrls)
              System.out.println(url);
          System.out.println("-----------boot-----------------");
    
         URLClassLoader extClassLoader = (URLClassLoader)Thread.currentThread().getContextClassLoader().getParent();
          URL[] extUrls  = extClassLoader.getURLs();
          System.out.println("-----------ext-----------------");
          for(URL url : extUrls)
              System.out.println(url);
          System.out.println("-----------ext-----------------");
    
          URLClassLoader appClassLoad = (URLClassLoader)Thread.currentThread().getContextClassLoader();
          URL[] appClassLoadUrls  = appClassLoad.getURLs();
          System.out.println("-----------appClassloader-----------------");
          for(URL url : appClassLoadUrls)
            System.out.println(url);
          System.out.println("-----------appClassloader-----------------");
    
          String var1 = System.getProperty("java.class.path");
          System.out.println("classpath:"+var1);
      }
    
    
    
    
    

    ClassLoader如何发现资源   

        
    ①:A.class.getResourceAsStream(resource)
    ②:A.class.getClassLoader().getResourceAsStream("")
    ③:Class.forName("").newInstance();
          上面的代码大家熟悉不,我相信很多人都用过,但是用的时候都很迷茫,每次用的时候一旦出错,都不知道为啥出错,怎么修改(内心都是why? why? why?)到底是为啥呀 ???下面我们就聊聊这个Classloader的原理

    这段是基础介绍,如果大家知道可以跳过
     一,各个Classloader的关系直接上图(双亲委托机制)

    
    
    

                查找一个.class文件时,从上图可以看到,

                ①:查找资源的时候appClassloader会将查找任务先委托给自己的上级ExtClassloader

                ②:ExtClassloader会将查找任务先委托给上级BootStrapClassloader

                ③:只有在上级查找不到情况下,自己再负责查找资源并加载

                经典加载代码 Thread.currentThread().getContextClassLoader().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;
        }
    }
    
    
    
     
    这段是基础介绍,如果大家知道可以跳过

    问题
        问题1:每个classloader 管理的资源存储在什么地方
        问题2:项目依赖的jar包本身还要依赖第三jar包,此时classloader又该如何处理
    示例
     main方法执行后,JVM是如何加载main方法所在的类
    首先大家要知道JVM是不会傻傻的在启动时候,直接将程序中所有的类和资源都加载进内存,JVM秉承的设计原则就是,用时再加载,不用的时候,你就歇着吧(JVM作为BOSS 很抠的哈),那么JVM是怎么加载main方法所在的类尼
    ①:程序启动的时候,JVM负责先装载所有的classLoader(这一部分,大家不用深究,具体JVM怎么下指令的,这个东西是底层做的,我们也看不懂),我们只需要知道JVM帮忙实例化了sun.misc.Launcher这个类,在这个构造函数中
    ,装载所有的Classloader(主意:BootstrapClassloader不归它管,你可以理解BootstrapClassloader为JVM直辖(就像直辖市一样))
    public Launcher() {
        Launcher.ExtClassLoader var1;
        try {
            //加载ExtClassload
            var1 = Launcher.ExtClassLoader.getExtClassLoader();
        } catch (IOException var10) {
            throw new InternalError("Could not create extension class loader", var10);
        }
    
        try {
            //加载AppClassLoad
            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);
        }
    
    }   

    ②:加载main方法所在的类如:A,调用的部分流程如下:最终调用Classloader.loadClass这个方法加载A类,这个类(典型的双亲委托模式实例)

                 

                     

           

               ③:带着问题分析一下ClassLoader.loadClass 这个类

                从上文得知findClass() 这个方法是具体负责加载资源,但是你点击进去会发现这是一个为空实现,看看这个类的继承关系,AppClassLoader父类为URLClassLoader,我们到URLclassLoader上看看

     

               其实到这里,我们已经可以看到,其实是类查找和加载UCP 这个对象操作,UCP(是URLClasspath这个类的实例

         ps:这里的涉及到缓存的代码,暂时不要看(如:getLookUpCache()),基本上都无法名中(反正我debug的时候,没啥用)

     

     重点来啦,要看懂上述这段代码,就不的不提一下上面讲的那个类URLClassPath 和URLClassPath.loader这个内部类

     

     path:初始化类加载器的时候,设置类记载器管理的默认资源列表(比如appclassLoader 这个类加载器,这个path中主要存储的是项目依赖的jar包路径(不包括此ar包依赖的第三方jar),jre/lib下的部分jar

     urls:其实初始化完成以后应该和path一样,区别在于urls还会解决这个问题(问题2:项目依赖的jar包本身还要依赖第三jar包,此时classloader又该如何处理),

               也就是说,urls初始化完成以后,还会去加载jar包依赖的第三方jar包,怎么加载的(下面分析)(这是一个栈的数据结构,先进后出

    loaders:这个属性有点强啦,是资源抽象(URLClassPath.loader)的列表,提供一个非常关键的方法  Resource getResource(String var1, boolean var2) ,负责匹配资源(或者说在这个资源中能不能找到这个要加载的类

                   URLClassPath.loader  有2个具体的实现类,每个都重写了getResource这个方法

     lamp 这个其实作用不是很大,做一个缓存的作用,如果URLClassPath.loader实例化过啦,直接从缓存中取出,不用重新实例化啦

    
    

    那么我们现在再来分析一下,getLoader这个方法

    
    
    private synchronized URLClassPath.Loader getLoader(int var1) {
        if(this.closed) {
            return null;
        } else {
             //这里不用if用while,原因是loader存在重复加载,此时缓存已经有啦(证明这个loader是无法找到这个资源),
             //直接获取下一个loader即可(项目依赖的jar包很多,存在多个jar包在依赖其他的同一个jar包很正常,
           //如:每个jar包都需要日志相关的jar)
            while(this.loaders.size() < var1 + 1) {
                Stack var3 = this.urls;
                URL var2;
                synchronized(this.urls) {
                     //urls都空即当前这个classloader无法加载这个类
                    if(this.urls.empty()) {
                        return null;
                    }
    
                    var2 = (URL)this.urls.pop();
                }
    
                String var9 = URLUtil.urlNoFragString(var2);
                if(!this.lmap.containsKey(var9)) {
                    URLClassPath.Loader var4;
                    try {
                        var4 = this.getLoader(var2);
                        //这个地方主要是解决问题2(有兴趣可以看看它的实现类,JarLoader,简单滴讲就是读取MANIFEST.MF中的classpath这个字段),问题2:项目依赖的jar包本身还要依赖第三jar包,此时classloader又该如何处理
                        URL[] var5 = var4.getClassPath();
                        if(var5 != null) {
                            this.push(var5);
                        }
                    } catch (IOException var6) {
                        continue;
                    } catch (SecurityException var7) {
                        if(DEBUG) {
                            System.err.println("Failed to access " + var2 + ", " + var7);
                        }
                        continue;
                    }
    
                    this.validateLookupCache(this.loaders.size(), var9);
                    this.loaders.add(var4);
                    this.lmap.put(var9, var4);
                }
            }
    
            if(DEBUG_LOOKUP_CACHE) {
                System.out.println("NOCACHE: Loading from : " + var1);
            }
            return (URLClassPath.Loader)this.loaders.get(var1);
        }
    }
    
    
    


    给大家画了一张简单时序图


    简单总结
    整个classloader加载思想就是:将classloader能够管理的资源交给URLClasspath,将查找这个脏活交给这个loader内部类,通过调用getResource()来确定是否有能力加载
    
    
  • 相关阅读:
    Spring Boot 结合 Redis 序列化配置的一些问题
    基于SpringBoot的代码在线运行的简单实现
    将Spring实战第5版中Spring HATEOAS部分代码迁移到Spring HATEOAS 1.0
    用Spring Security, JWT, Vue实现一个前后端分离无状态认证Demo
    使用最新AndroidStudio编写Android编程权威指南(第3版)中的代码会遇到的一些问题
    C# 内存管理优化畅想----前言
    C# 内存管理优化实践
    C# 内存管理优化畅想(三)---- 其他方法&结语
    C# 内存管理优化畅想(二)---- 巧用堆栈
    C# 内存管理优化畅想(一)---- 大对象堆(LOH)的压缩
  • 原文地址:https://www.cnblogs.com/huxuhong/p/12769460.html
Copyright © 2011-2022 走看看