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对象是不一样的。

  • 相关阅读:
    UVA 11488 Hyper Prefix Sets (字典树)
    UVALive 3295 Counting Triangles
    POJ 2752 Seek the Name, Seek the Fame (KMP)
    UVA 11584 Partitioning by Palindromes (字符串区间dp)
    UVA 11100 The Trip, 2007 (贪心)
    JXNU暑期选拔赛
    计蒜客---N的-2进制表示
    计蒜客---线段的总长
    计蒜客---最大质因数
    JustOj 2009: P1016 (dp)
  • 原文地址:https://www.cnblogs.com/ln9937/p/11414496.html
Copyright © 2011-2022 走看看