美文网首页
3. JVM类加载器

3. JVM类加载器

作者: 轻轻敲醒沉睡的心灵 | 来源:发表于2021-07-16 16:29 被阅读0次

我们写的.Java源代码,机器是不是别的。源代码打包会经过java编译器生成.class文件,JVM虚拟机是通过加载这些.class文件而实现加载我们的类文件的。

1. .class类文件的生命周期

类的生命周期有7个步骤:

    1. 加载(Loading):找 Class 文件
    1. 验证(Verification):验证格式、依赖
    1. 准备(Preparation):静态字段、方法表
    1. 解析(Resolution):符号解析为引用
    1. 初始化(Initialization):构造器、静态变量赋值、静态代码块
    1. 使用(Using)
    1. 卸载(Unloading)


      类生命周期.png

2. 类的加载时机

Java虚拟机对class文件采用的是按需加载的方式,也就是说当需要使用该类时才会将它的class文件加载到内存生成class对象。下面是几种类加载时机:

  1. 当虚拟机启动时,初始化用户指定的主类,就是启动执行的 main 方法所在的类;
  2. 当遇到用以新建目标类实例的 new 指令时,初始化 new 指令的目标类,就是 new一个类的时候要初始化;
  3. 当遇到调用静态方法的指令时,初始化该静态方法所在的类;
  4. 当遇到访问静态字段的指令时,初始化该静态字段所在的类;
  5. 子类的初始化会触发父类的初始化;
  6. 如果一个接口定义了 default 方法,那么直接实现或者间接实现该接口的类的初始化,会触发该接口的初始化;
  7. 使用反射 API 对某个类进行反射调用时,初始化这个类,其实跟前面一样,反射调用要么是已经有实例了,要么是静态方法,都需要初始化;
  8. 当初次调用 MethodHandle 实例时,初始化该 MethodHandle 指向的方法所在的类。

注意:有些时候用到类中一些东西,但是不会初始化该类,下面几种情况就是

1. 通过子类引用父类的静态字段,只会触发父类的初始化,而不会触发子类的初始化。
2. 定义对象数组,不会触发该类的初始化。
3. 常量在编译期间会存入调用类的常量池中,本质上并没有直接引用定义常量的类,不会触发定义常量所在的类。
4. 通过类名获取 Class 对象,不会触发类的初始化,Hello.class 不会让 Hello 类初始化。
5. 通过 Class.forName 加载指定类时,如果指定参数 initialize 为 false 时,也不会触发类初始化,其实这个参数是告诉虚拟机,是否要对类进行初始化。Class.forName(“jvm.Hello”)默认会加载 Hello 类。
6. 通过 ClassLoader 默认的 loadClass 方法,也不会触发初始化动作(加载了,但是不初始化)。

3. 类加载的双亲委派机制

3.1 JVM中的3种类加载器

JVM中提供了3种类加载器(ClassLoader):启动类加载器(BootstrapClassLoader)、扩展类加载器(ExtClassLoader)和 应用类加载器(AppClassLoader) 。3种类加载器是有级别的:启动类加载器 > 扩展类加载器 > 应用类加载器。当然,我们还可以自定义加载器,自定义的加载器可以归类到 第4种,JVM中是没有的。

  • BootstrapClassLoader(启动类加载器)
    c++编写,主要负责加载核心的类库(java.lang.*等),构造ExtClassLoaderAPPClassLoader
  • ExtClassLoader (扩展类加载器)
    java编写,加载扩展库,如classpath中的jre/lib/ext,javax.*或者java.ext.dir 指定位置中的类,开发者可以直接使用扩展类加载器。
  • AppClassLoader(应用类加载器)
    主要负责加载应用程序的主函数类

所以,如果想要添加引用类,主要通过这几个方式:

    1. 放到 JDK 的 lib/ext 下,或者-Djava.ext.dirs
  • 2、 java –cp/classpath 或者 class 文件放到当前路径
  • 3、自定义 ClassLoader 加载
  • 4、拿到当前执行类的 ClassLoader,反射调用 addUrl 方法添加 Jar 或路径(JDK9 无效)。

3.2 双亲委派机制

举个栗子,当一个类Hello.class文件要被加载时(不考虑我们自定义类加载器),首先会在AppClassLoader中检查是否加载过,如果有那就无需再加载了;如果没有,那么会拿到父加载器,然后调用父加载器的loadClass方法。父类中同理也会先检查自己是否已经加载过,如果没有再往上。注意这个类似递归的过程,直到到达Bootstrap classLoader之前,都是在检查是否加载过,并不会选择自己去加载。直到BootstrapClassLoader,已经没有父加载器了,这时候开始考虑自己是否能加载了,如果自己无法加载,会下沉到子加载器去加载,一直到最底层,如果没有任何加载器能加载,就会抛出ClassNotFoundException。具体示意图如下:

双亲委派机制.png

使用双亲委派机制,主要2方面作用:

    1. 防止重复加载类,相当于我们设计中的缓存,先从缓存中找,找不到再去加载
    1. 沙箱安全机制:如果有人想替换系统级别的类:String.java。篡改它的实现,在这种机制下这些系统的类已经被Bootstrap classLoader加载过了(为什么?因为当一个类需要加载的时候,最先去尝试加载的就是BootstrapClassLoader),所以其他类加载器并没有机会再去加载,从一定程度上防止了危险代码的植入。

4. 自定义 ClassLoader

。。。。。。

相关文章

网友评论

      本文标题:3. JVM类加载器

      本文链接:https://www.haomeiwen.com/subject/qfsfpltx.html