JVM的类加载器(详细)

一、类加载器的关系

在这里插入图片描述

二、类加载器的分类

  • 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 ClassLoader\AppClassLoader)
    • 派生于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对象,加载的过程中使用的双亲委派模式,即把请求交给父类处理。

    1. 如果一个类加载器收到了类加载的请求,它不会自己加载,而是先把这个请求给自己的父类加载器去执行

    2. 如果这个父类加载器还有父类加载器,则会再将请求给自己的父类加载器,依次递归到顶层的启动类加载器

    3. 依次进行判断是否能完成委派(加载此类),若能完成委派则该类就由此加载器加载,若无法完成委派,则将委托给子类加载器进行判断是否能完成委派,依次递归到底层加载器,若期间被加载则完成加载阶段不会再递归(注)。

在这里插入图片描述

 注:类只能被一个加载器所加载。
  • 双亲委派的优势

    • 避免类的重复加载

    • 保护程序的安全,防止核心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种:

  1. 由于双亲委派模型是在JDK1.2之后才引入的,所以在JDK1.2之前是不符合双亲委派模型的:

    • ClassLoader这类在JDK1.0开始有存在的,在JDK1.2之前,ClassLoader中是通过私有方法loadClassInternal()去调用自己内部的loadClass()。为了满足双亲委派以及向下兼容,在JDK1.2后的ClassLoader类中,又为该类添加了protected的findClass()方法,JDK1.2之后就不推荐通过覆盖重写loadClass()方法了,而是在新添加的findClass()方法中书写自己的类加载逻辑,若loadClass()方法中的父类加载失败则会调用自己的findClass()方法。
  2. 由于双亲委派模型的旨意是越核心的类越由高层的加载器所加载(上文提到过的String类),倘若这些核心类要去调用用户的基础类,例如JNDI服务(是对 资源进行集中管理和查找,它需要调用由独立厂商实现并部署在应用程序的ClassPath下的JNDI接口提供者(SPI,Service Provider Interface)的代码,但启动类加载器不可能"认识"这些代码)

    • 为了解决调用问题,设计了一个线程上下文类加载器(Thread Context ClassLoader),这个类加载器可以通过java.lang.Thread类的setContextClassLoaser()方法进行设置,如果创建线程时还未设置,它将会从父线程中继承一个,如果在应用程序的全局范围内都没有设置过的话,那这个类加载器默认就是应用程序类加载器。
    • 有了这个上下文类加载器,就可以去加载所需要的SPI代码,实际上就是从父类加载器去请求子类加载器去完成类的加载驱动,违背了双亲委派的一般性规则。Java中所有涉及SPI的加载动作基本上都采用这种方式,例如JNDI、JDBC、JCE、JAXB和JBI等。在这里插入图片描述
  3. 为了满足"热部署"、"动态部署"等功能而导致的。在OSGi(动态模块技术)环境下,类加载器不再是双亲委派模型中的树状结构,而是进一步发展为更加复杂的网状结构,当收到类加载请求时,OSGi将按照顺序进行类搜索(其中搜索破坏了双亲委派规则,详细在《深入java虚拟机》p265)。

六、关于类加载器的一些补充

  1. JVM中判断一个类是否是同一个类有两个必要条件:
    1. 这两个类的全限定名要一致
    2. 这两个类被同一个类加载器加载。
  2. 对类加载器的引用:
    • JVM必须知道一个类型是由引导类加载器(启动类加载器)加载的还是由用户类加载器加载的。如果一个类是由用户类加载器所加载的,那么**JVM会将这个类加载器的一个引用作为类信息的一部分保存在方法区。**当解析一个类型到另外一个类型的引用的时候,JVM需要保证这两个类型的类加载器是相同的。
  3. 类的主动使用和被动使用
    • 主动使用:
      • 创建类的实例
      • 访问某个类或接口的静态变量,或者对该静态变量赋值
      • 调用类的静态方法
      • 反射
      • 初始化一个类的子类
      • JVM启动时被标明为启动类的类
      • JDK 7 开始提供的动态代理:java.invoke.MethodHandle实例的解析结果,REF_getStatic、REF_putStatic、REF_invokeStatic句柄对应的类没有初始化、则初始化
    • 除以上7种情况,其他使用Java类的方式都为被动使用,被动使用不会导致类的初始化。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值