zoukankan      html  css  js  c++  java
  • 面试BAT必问的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 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类的方式都为被动使用,被动使用不会导致类的初始化。

    最后

    大家看完有什么不懂的可以在下方留言讨论,也可以关注我私信问我,我看到后都会回答的。也欢迎大家关注我的公众号:前程有光,马上金九银十跳槽面试季,整理了1000多道将近500多页pdf文档的Java面试题资料放在里面,助你圆梦BAT!文章都会在里面更新,整理的资料也会放在里面。谢谢你的观看,觉得文章对你有帮助的话记得关注我点个赞支持一下!

  • 相关阅读:
    foreach
    if
    注意事项
    Maven测试
    课程评价
    个人总结
    HTML表格CSS美化
    让多个输入框对齐
    CSS样式写在JSP代码中的几种方法
    日常
  • 原文地址:https://www.cnblogs.com/lwh1019/p/13553522.html
Copyright © 2011-2022 走看看