zoukankan      html  css  js  c++  java
  • Java ClassLoader

    ClassLoader教程

    1. 简介

    Classloader的核心作用就是将编译之后的class文件加载到jvm运行的内存当中。在jvm的规范当中,类加载器主要分为三种:引导类加载器(BootClassLoader)、扩展类加载器(ExtClassLoader)、系统类加载器(AppClassLoader)。

    当运行一个程序的时候,JVM(Java虚拟机)启动,运行bootstrapclassloader,该ClassLoader加载java核心API(ExtClassLoader和AppClassLoader也在此时被加载),然后调用ExtClassLoader加载扩展API,最后AppClassLoader加载CLASSPATH目录下定义的Class,这就是一个程序最基本的加载流程。

    2. 加载器分类

    2. 1引导类加载器

    引导类加载器主要加载的class是jdk本身的类库(JAVA_HOMElib目录下的jar文件),这些类库都是jdk核心的类库(rt.jar),也是最重要的,因此由这个加载器去完成加载和初始化。并且这个加载器是使用C++语言实现的。

    2.2 扩展类加载器

    扩展类加载器的主要租用就是加载jdk的扩展包的jar文件(JAVA_HOMElibext目下的jar文件),这些文件是JDK的扩展类库。//可以由开发者自主调用

    2.3 系统类加载器

    系统了加载器的核心作用就是加载classpath路径下的class文件以及jar文件,这些类库通常由开发人员自己编写的,因此这些类都是通过系统类加载完成加载。

    3. 委托模式

    在执行类加载的时候,最先由系统类加载器开始,但它会把加载的执行权先交给扩展类加载器,同样,扩展类加载器也会将加载的执行权交给引导类加载器,最终由引导类加载器开始加载,如果需要加载的类库不是引导类加载器加载的范畴,那么就会将加载权交回给扩展类加载器。同样,如果类库不是扩展类加载器加载的范畴,最后就交由给系统类加载器来完成,这个过程就是委托。这样做的目的是为了保证系统类库加载时的安全性,如:Object、String类等等,这些都应该由引导类加载器来完成,不应该交由其他类加载器,因为,如果这些类库加载的后可以由用户来进行操作,那么就可会导致类的不安全。例如:用户可以重载或修改类中的方法,这是绝对禁止的。

    优点:

    • 使得类加载不会重复加载。

    • 具有优先级层次关系,使得java程序稳定运作。

      • 举个栗子,如果我们使用了一个自定义的类全限定名一样的Object类,虽然编译不会报错,但是运行时就会报错,是因为这种双亲委派机制的存在,即使自定义Object,也会委托给最顶层的启动类加载器,该类加载器发现这个类不合法,抛出运行时异常。倘若不是双亲委托,而是系统类加载器或者自定义类加载器加载Object,因为加载过的类会缓存,会导致其他继承了Object类的类受到巨大影响。

    4. ClassLoader的常见API

    除了引导到类加载器是有C语言来实现以外,其他的类加载器都是继承自ClassLoader这个类,而这个类中提供了相应的加载API来完成类加载以及初解析和始化的过程。

    API说明
    getParent() 获取上一级的类加载器
    loadClass(String name) 加载名称为name的类,返回的是一个Class实例,在此方法中会间接调用findClass方法
    findClass(String name) 加载名称为name的类,返回的是一个Class实例。通常自定义类加载器时,会重写此方法。
    findLoadedClass(String name) 检查名称为name的Class是否已经加载过,返回的是一个Class实例,如果Class为null,就表示未加载,否则就是已经加载
    defineClass(String name, byte[] b, int off, int len) 将字节数组b的二进制数据转换成Java中的Class对象
    resolveClass(Class c) 解析并连接到指定的Class

     

       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 {  
                       //如果父加载器不为空,使用父加载器加载class  
                       if (parent != null) {  
                           c = parent.loadClass(name, false);  
                      } else {  
                           //如果父加载器也为空,使用bootstarpClassLoader加载  
                           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;  
      }  
    }  

    5. 自定义类加载器

    我们也可以自定义类加载器,只需要继承ClassLoader,通常需要重写父类的findClass方法。需要注意的是,自定义类加载器它的上一级的类加载器是AppClassLoader,也会遵循委托的模式。

    什么时候需要自定义类加载器

    • 我们需要的类不一定存放在已经设置好的classPath下(有系统类加载器AppClassLoader加载的路径),对于自定义路径中的class类文件的加载,我们需要自己的ClassLoader

    • 有时我们不一定是从类文件中读取类,可能是从网络的输入流中读取类,这就需要做一些加密和解密操作,这就需要自己实现加载类的逻辑,当然其他的特殊处理也同样适用。

    • 可以定义类的实现机制,实现类的热部署,如OSGi中的bundle模块就是通过实现自己的ClassLoader实现的。

    示例:

    public class DemoClassLoader extends ClassLoader {
       /**
        * 类加载的根路径
        */
       private String loadRootPath;

       public DemoClassLoader(String loadRootPath){
           this.loadRootPath = loadRootPath;
      }

       /**
        * 重写父类的findClass方法
        * @param name 需要加载的完整类名
        * @return
        * @throws ClassNotFoundException
        */
       @Override
       protected Class<?> findClass(String name) throws ClassNotFoundException {
           //构建类文件的绝对路径,用于I/O流读写操作
           String loadPath = loadRootPath + convertNameToPath(name);
           //通过输入流将class文件读入内存当中
           File file = new File(loadPath);
           byte[] bytes = readClassFile(file);
           //将直接数组转换为Class对象
           Class<?> clazz = defineClass(name, bytes, 0, bytes.length);
           return clazz;
      }

       /**
        * 根据File对象将class文件读入内存,返回一个字节数组
        * @param file
        * @return
        */
       private byte[] readClassFile(File file){
           //构建文件输入流
           try(FileInputStream fis = new FileInputStream(file);
               ByteArrayOutputStream baos = new ByteArrayOutputStream()){
               int len = 0;
               byte[] bytes = new byte[1024];
               while((len = fis.read(bytes, 0, bytes.length)) != -1){
                       //将读取的直接保存在一个缓存中
                   baos.write(bytes, 0, len);
              }
               //将流中的直接数组直接返回
               return baos.toByteArray();
          }catch (IOException e){
               e.printStackTrace();
               throw new RuntimeException(e);
          }
      }

       /**
        * 将完整类名转换为一个相对路径
        * @return
        */
       private String convertNameToPath(String name){
           String path = name.replace(".", File.separator) + ".class";
           return path;
      }
    }

    使用:

    public class Main {
       public static void main(String[] args) throws Exception{
        //输入需要加载的类的所在地址
           DemoClassLoader cl = new DemoClassLoader("/Users/wangl/");
           Class clazz = cl.loadClass("Hello");
           Object hello = clazz.newInstance();
           Object hello2 = clazz.newInstance();
           System.out.println(hello);
           System.out.println(hello2);
           System.out.println(clazz.getClassLoader());
      }
    }

    注意:如果是不同的类加载器加载同一个class文件,那么产生的Class对象是不一样的。

  • 相关阅读:
    Python-常用的高级函数
    Excel
    业务思维
    数据分析思维
    Netbeans 12无法打开项目(project的)的问题
    C++ tuple元组
    如何保障一场千万级大型直播?
    回声消除的昨天、今天和明天
    无参考评估在云信的视频测试实践
    一文读懂Python 高阶函数
  • 原文地址:https://www.cnblogs.com/ln9937/p/11414496.html
Copyright © 2011-2022 走看看