乐趣区

关于java:Java类加载器的底层原理

类加载器的关系

类加载器的分类

  • JVM 反对两品种加载器,一种为 疏导类加载器 (Bootstrap ClassLoader), 另外一种是 自定义类加载器(User Defined ClassLoader)
  • 疏导类加载器是由 C /C++ 编写的无法访问到
  • Java 虚拟机规定:所有派生于抽象类 ClassLoader 的类加载器都划分为自定义加载器
  • 最常见的类加载器只有三个(如上图所示)

用户自定义的类会被零碎类加载器所加载,外围类库的类会被疏导类加载器所加载

public class ClassLoaderTest {public static void main(String[] args) {
        // 获取零碎类加载器
        ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
        System.out.println(systemClassLoader);//sun.misc.Launcher$AppClassLoader@18b4aac2

        // 获取其下层:扩大类加载器
        ClassLoader extClassLoader = systemClassLoader.getParent();
        System.out.println(extClassLoader);//sun.misc.Launcher$ExtClassLoader@1540e19d

        // 获取其下层:获取不到疏导类加载器
        ClassLoader bootstrapClassLoader = extClassLoader.getParent();
        System.out.println(bootstrapClassLoader);//null

        // 对于用户自定义类来说:默认应用零碎类加载器进行加载
        ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
        System.out.println(classLoader);//sun.misc.Launcher$AppClassLoader@18b4aac2

        //String 类应用疏导类加载器进行加载的。---> Java 的外围类库都是应用疏导类加载器进行加载的。ClassLoader classLoader1 = String.class.getClassLoader();
        System.out.println(classLoader1);//null
    }
}

零碎自带的类加载器介绍

  • 启动类加载器(疏导类加载器、Bootstrap ClassLoader)
    • 由 c /c++ 语言实现的,嵌套在 jvm 外部
    • 用来加载 java 外围库
    • 并不继承 java.lang.ClassLoader,没有父加载器
    • 为扩大类加载器和零碎类加载器的父加载器
    • 只能加载 java、javax、sun 结尾的类
  • 扩大类加载器(Extension ClassLoader)
    • java 语言编写,sun.misc.Launche 包下。
    • 派生于 ClassLoader 类,父类加载器为 Bootstrap ClassLoader
    • 从 java.ext.dirs 零碎属性指定的目录中加载类库或者加载 jre/lib/ext 子目录下的类库(用户能够在该目录下编写 JAR,也会由此加载器所加载)
  • 零碎类加载器(System ClassLoaderAppClassLoader)
    • 派生于 ClassLoader,父类加载器为 Extension ClassLoader
    • 负责加载 classpath 或者零碎属性 java.class.path 指定门路下的类库
    • java 语言编写,sun.misc.Launche 包下。
    • 负责加载程序中默认的类,能够通过 getSystemClassLoader()办法获取该类的加载器。
  • 用户自定义类加载器(前面具体介绍)
    • 隔离加载类
    • 批改类加载的形式
    • 扩大加载源
    • 避免源码透露(能够对字节码文件加密)
    • 继承 ClassLoader 类形式实现自定义类加载器

对于 ClassLoader

  • ClassLoader 是一个抽象类,其后的所有类加载器都继承此类

注:这些办法都不是形象办法。

获取 ClassLoader 的门路

public class ClassLoaderTest2 {public static void main(String[] args) {
      try {
          //1.
          ClassLoader classLoader = Class.forName("java.lang.String").getClassLoader();
          System.out.println(classLoader);
          //2.
          ClassLoader classLoader1 = Thread.currentThread().getContextClassLoader();
          System.out.println(classLoader1);
          //3.
          ClassLoader classLoader2 = ClassLoader.getSystemClassLoader().getParent();
          System.out.println(classLoader2);
      } catch (ClassNotFoundException e) {e.printStackTrace();
      }
  }
}

双亲委派机制(面试)

  • Java 虚拟机对 class 文件采纳的是 按需加载 的形式,当须要应用到这个类的时候才会对它的 class 文件加载到内存生成 class 对象,加载的过程中应用的 双亲委派模式,即把申请交给父类解决。
  • 如果一个类加载器收到了类加载的申请,它不会本人加载,而是先把这个申请给本人的父类加载器去执行
  • 如果这个父类加载器还有父类加载器,则会再将申请给本人的父类加载器,顺次递归到顶层的启动类加载器
  • 顺次进行判断是否能实现委派(加载此类),若能实现委派则该类就由此加载器加载,若无奈实现委派,则将委托给子类加载器进行判断是否能实现委派,顺次递归到底层加载器,若期间被加载则实现加载阶段不会再递归(注)。

注:类只能被一个加载器所加载。
  • 双亲委派的劣势
    • 防止类的反复加载
  • 爱护程序的平安,避免外围 API 被篡改
  • 例如:

    • 创立一个 java.lang.String 类,因为有双亲委派的机制,所以会将 String 类交给疏导类加载器来判断是否能被加载。疏导加载器判断能够加载此类(是外围类中的 String),实现加载,则 ” 歹意 ” 写的 String 类无奈失效,避免 String 类被歹意篡改。这里也称沙箱平安机制(爱护 java 外围源代码)
package java.lang;
public class String {
    //
    static{System.out.println("我是自定义的 String 类的动态代码块");
    }
    // 谬误: 在类 java.lang.String 中找不到 main 办法
    public static void main(String[] args) {System.out.println("hello,String");
    }
}

// 因为加载的是外围类的 String,在 String 中找不到 main 办法 
public class StringTest {public static void main(String[] args) {java.lang.String str = new java.lang.String();// 无输入
        
        StringTest test = new StringTest();
        System.out.println(test.getClass().getClassLoader());
    }
}
package java.lang;

public class ShkStart {

    // 谬误:java.lang.SecurityException: Prohibited package name: java.lang
    public static void main(String[] args) {System.out.println("hello!");
    }
}

// 因为 java.lang 包由疏导类加载器加载,疏导类中并没有此类,为了平安疏导类 

毁坏双亲委派模型:

较大规模的毁坏双亲委派模型的有 3 种:

  • 因为双亲委派模型是在 JDK1.2 之后才引入的,所以在 JDK1.2 之前是不合乎双亲委派模型的:
  • ClassLoader 这类在 JDK1.0 开始有存在的,在 JDK1.2 之前,ClassLoader 中是通过公有办法 loadClassInternal()去调用本人外部的 loadClass()。为了满足双亲委派以及向下兼容,在 JDK1.2 后的 ClassLoader 类中,又为该类增加了 protected 的 findClass()办法,JDK1.2 之后就不举荐通过笼罩重写 loadClass()办法了,而是在新增加的 findClass()办法中书写本人的类加载逻辑,若 loadClass()办法中的父类加载失败则会调用本人的 findClass()办法。
  • 因为双亲委派模型的旨意是越外围的类越由高层的加载器所加载(上文提到过的 String 类),假使这些外围类要去调用用户的根底类,例如 JNDI 服务(是对 资源进行集中管理和查找,它须要调用由独立厂商实现并部署在应用程序的 ClassPath 下的 JNDI 接口提供者(SPI,Service Provider Interface)的代码,但启动类加载器不可能 ” 意识 ” 这些代码)

    • 为了解决调用问题,设计了一个线程上下文类加载器(Thread Context ClassLoader),这个类加载器能够通过 java.lang.Thread 类的 setContextClassLoaser()办法进行设置,如果创立线程时还未设置,它将会从父线程中继承一个,如果在应用程序的全局范畴内都没有设置过的话,那这个类加载器默认就是应用程序类加载器。
    • 有了这个上下文类加载器,就能够去加载所须要的 SPI 代码,实际上就是从父类加载器去申请子类加载器去实现类的加载驱动,违反了双亲委派的一般性规定。Java 中所有波及 SPI 的加载动作基本上都采纳这种形式,例如 JNDI、JDBC、JCE、JAXB 和 JBI 等。
  • 为了满足 ” 热部署 ”、” 动静部署 ” 等性能而导致的。在 OSGi(动静模块技术)环境下,类加载器不再是双亲委派模型中的树状构造,而是进一步倒退为更加简单的网状结构,当收到类加载申请时,OSGi 将依照程序进行类搜寻

对于类加载器的一些补充

1. JVM 中判断一个类是否是同一个类有两个必要条件:
  • 这两个类的全限定名要统一
  • 这两个类被同一个类加载器加载。
2. 对类加载器的援用:
  • JVM 必须晓得一个类型是由疏导类加载器(启动类加载器)加载的还是由用户类加载器加载的。
  • 如果一个类是由用户类加载器所加载的,那么 JVM 会将这个类加载器的一个援用作为类信息的一部分保留在办法区。
  • 当解析一个类型到另外一个类型的援用的时候,JVM 须要保障这两个类型的类加载器是雷同的。

    3. 类的被动应用和被动应用
    • 被动应用:

      • 创立类的实例
      • 拜访某个类或接口的动态变量,或者对该动态变量赋值
      • 调用类的静态方法
      • 反射
        • 初始化一个类的子类
        • JVM 启动时被表明为启动类的类
      • JDK 7 开始提供的动静代理:java.invoke.MethodHandle 实例的解析后果,REF_getStatic、REF_putStatic、REF_invokeStatic 句柄对应的类没有初始化、则初始化

        • 除以上 7 种状况,其余应用 Java 类的形式都为被动应用,被动应用不会导致类的初始化。
退出移动版