2.java类加载器

类加载器

类加载器并不需要等待某个类在“首次主动使用”的时候才区加载它。因为:

jvm规范容许类加载器在预料某个类将要被使用的时候就预先加载它,如果在预先加载的时候遇到了.class文件确实或者存在错误,类加载器必须在程序首次主动使用该类的时候才报错。如果这个类一直没有被程序使用那么就不会报错。

类加载器负责加载所有的类,其为所有被载入内存中的类生成一个java.lang.Class实例对象。一旦一个类被加载如JVM中,同一个类就不会被再次载入了。正如一个对象有一个唯一的标识一样,一个载入JVM的类也有一个唯一的标识。在Java中,一个类用其全限定类名(包括包名和类名)作为标识;但在JVM中,一个类用其全限定类名和其类加载器作为其唯一标识。例如,如果在pg的包中有一个名为Person的类,被类加载器ClassLoader的实例kl负责加载,则该Person类对应的Class对象在JVM中表示为(Person.pg.kl)。这意味着两个类加载器加载的同名类:(Person.pg.kl)和(Person.pg.kl2)是不同的、它们所加载的类也是完全不同、互不兼容的。

有两中国类加载器

  • Java虚拟机自带的类加载器
    • 根类加载器(bootstrap class loader)

      它用来加载 Java 的核心类,是用原生代码来实现的,并不继承自 java.lang.ClassLoader(负责加载$JAVA_HOME中jre/lib/rt.jar里所有的class,由C++实现,不是ClassLoader子类)。由于引导类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用,所以不允许直接通过引用进行操作。

    • 扩展类加载器(extensions class loader)

      它的父加载器为根类加载器,它负责加载JRE的扩展目录,lib/ext或者由java.ext.dirs系统属性指定的目录中的JAR包的类。由Java语言实现,是java.lang.ClassLoader类的子类,扩展类加载器智能加载扩展类加载器加载路径下的jar文件,不能加载class文件。(如果想加载class文件需要先打成jar包放在扩展类加载器加载的目录下才能加载)

    • 系统类加载器(system class loader)

      被称为系统(也称为应用)类加载器,它的父类加载器是扩展类加载器,它负责在JVM启动时加载来自Java命令的-classpath选项、java.class.path系统属性,或者CLASSPATH换将变量所指定的JAR包和类路径。程序可以通过ClassLoader的静态方法getSystemClassLoader()来获取系统类加载器。如果没有特别指定,则用户自定义的类加载器都以此类加载器作为父加载器。由Java语言实现,是java.lang.ClassLoader的子类 。

  • 三大类加载器所加载的范围

    public class MyTest18 {
        public static void main(String[] args) {
            System.out.println(System.getProperty("sun.boot.class.path"));//系统类加载器加载路径
            System.out.println(System.getProperty("java.ext.dirs"));//扩展类加载器加载路径
            System.out.println(System.getProperty("java.class.path"));//应用类加载器加载过程
        }
    }
    

    打印的结果比较长 这里说明一下 可以自己试试看一下打印结果。

    这里只显示一部分

    C:\Program Files\Java\jdk1.8.0_172\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_172\jre\lib\rt.jar;C:\Program Files\Java\jdk1.8.0_172\jre\lib\sunrsasign.jar;C:\Program Files\Java\jdk1.8.0_172\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_172\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_172\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_172\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_172\jre\classes
    
    C:\Program Files\Java\jdk1.8.0_172\jre\lib\ext;C:\WINDOWS\Sun\Java\lib\ext
    
    C:\Program Files\Java\jdk1.8.0_172\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_172\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_172\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_172\jre\lib\ext\cldrdata.jar;C:\Program ....
    
  • 用户自定义的类加载器
    • 除了虚拟机自带的类加载器,用户还可以定制自己的类加载器。Java提供了抽象类java.lang.ClassLoader,所有用户自定义的类加载器都应该继承ClassLoader类。下面是一个自定义的类加载器

      package com.fjh.jvm;
      
      import java.io.*;
      
      public class MyClassLoader extends ClassLoader {
          /** 自定义加载器的名字 */
          private String classLoaderName;
      
          private final String fileExtension = ".class";
      
          public MyClassLoader(String classLoaderName){
              // 指定父类加载器是system classLoader
              super();
              this.classLoaderName = classLoaderName;
          }
      
          public MyClassLoader(ClassLoader parent,String classLoaderName){
              // 显示指定该类加载器的父类加载器
              super(parent);
              this.classLoaderName = classLoaderName;
          }
      
          @Override
          protected Class<?> findClass(String className) throws ClassNotFoundException {
              System.out.println("findClass invoked:" + clasName);
              System.out.println("class loader name: " + this.className);
              byte[] data = loadClassData(className);
              return defineClass(className,data,0,data.length);
          }
      
          private byte[] loadClassData(String name){
              InputStream is = null;
              byte[] data = null;
              ByteArrayOutputStream bos = null;
      
              try{
                  this.classLoaderName.replace(".","/");
                  is = new FileInputStream(new File(name + this.classLoaderName));
                  bos = new ByteArrayOutputStream();
                  int ch = 0;
                  while (-1 != (ch = is.read())){
                      bos.write(ch);
                  }
                  data = bos.toByteArray();
      
              } catch (Exception ex){
                  ex.printStackTrace();
              } finally {
                  try {
                      is.close();
                      bos.close();
                  } catch (IOException e) {
                      e.printStackTrace();
                  }
              }
              return data;
          }
      
          public static void test(ClassLoader classLoader) throws Exception {
              Class<?> aClass = classLoader.loadClass("com.fjh.jvm.MyTest");
              Object o = aClass.newInstance();
              System.out.println(o);
          }
      
          public static void main(String[] args) throws Exception {
              MyClassLoader classLoader = new MyClassLoader("fjhClassLoad");
              test(classLoader);
          }
      
          @Override
          public String toString() {
              return "["+this.classLoaderName+"]";
          }
      }
      ===========运行结果===================
      com.fjh.jvm.MyTest@15db9742
      ======================================
         /** 说明并没有调用我们自己的类加载器,为什么呢,因为要加载的类在当前的classpath下面,根据类加载的双亲委托,系统类加载器是自定义类加载器的父加载器,说以还是系统类加载器加载了MyTest类。可以调用方法 。obj.getCLassLoader()方法看是由哪个类加载器加载的。那么怎么才能让自定义的加载器加载呢:1、编译之后把MyTest.class文件复制到其他的目录(只要不是当前工程的classPath即可),2、删除当前工程下classes文件夹下的MyTest.class,当然程序中的findClass里面的路径要写成新的目录。
      再执行,就会用自定义的类加载器加载。
      分析一下这是为什么:
      还是jvm类加载器的双亲委托机制:自定义的类加载器要加载这个类,首先看他的父类加载器(系统类加载器)是否加载了MyTest这个类,如果没有看系统类加载器的父类加载器(扩展类加载器)是否加载了MyTest,.......依次就会让系统类加载器去加载,系统类加载器在classes目录下并没有找到加载的文件MyTest.class文件,就会让自定义的类加载器去加载了。
      */
      

双亲委托机制

双亲委派机制,其工作原理的是,如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行,如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器,如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式。

双亲委派机制的优势:采用双亲委派模式的是好处是Java类随着它的类加载器一起具备了一种带有优先级的层次关系,通过这种层级关可以避免类的重复加载,当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次。其次是考虑到安全因素,java核心api中定义类型不会被随意替换,假设通过网络传递一个名为java.lang.Integer的类,通过双亲委托模式传递到启动类加载器,而启动类加载器在核心Java API发现这个名字的类,发现该类已被加载,并不会重新加载网络传递的过来的java.lang.Integer,而直接返回已加载过的Integer.class,这样便可以防止核心API库被随意篡改。

类加载双亲委托模型的好处:

1、可以确保Java核心库的类型安全:所有的Java应用都至少会引用Java.lang.Object类,也就是说在运行期,java.lang.Object这个类会被加载到Java虚拟机中,如果这个加载过程是由Java应用自己的类加载器所完成的,
那么很有可能就会在JVM中存在多个版本的java.lang.Object类,而且这些类库中的类的加载工作都是由启动类加载器来统一完成,从而确保了Java应用所使用的都是同一个版本的Java核心类库,他们之间是相互兼容的。
2、可以确保Java核心类库所提供的类不会被自定义的类所替代。
3、不同的类加载器可以为相同名称(binary name)的类创建额外的命名空间,相同名称的类可以并存在Java虚拟机中,只需要用不同的类加载器来加载他们即可。不同类加载器所加载的类之间是不兼容的,这就相当于在Java虚拟机内部创建了一个又一个相互隔离的Java类空间,这类技术在很多框架中得到了实际应用。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值