类加载器的关系

类加载器的分类

  • 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类的形式都为被动应用,被动应用不会导致类的初始化。