虚构机类加载机制

前言

Class文件结构既修了,今天来上下虚拟机如何加载Class文件。

C语音编译连接后一直就是不行成了可执行文件,程序执行,并不需要额外操作。但Java不均等,类型的加载与连续都是当程序运行期间完成的。

及时会招致额外的出,但马上为叫Java带来了极致的油滑,比如动态加载技术正是因这特性成就的。

正文主要要内容如下:

  • 接近加载时机
  • 仿佛加载过程
  • 仿佛加载器

类似加载时机

好像从于加载到虚拟机内存开始,到卸载出内存为止,一共会经历
加载、验证、准备、解析、初始化、使用、卸载 7独号。其中
验证、准备、解析 这三单解析被称为连接过程。

诚如的话,以上过程会仍地开始,但偏偏是初步,因为这些等级会陆续进行,通常会在一个阶段起后激活调用另一个经过。

以下4栽情形下,一定会起类的加载过程:

  • 遇上new、getstatic、putstatic、invokestatic等字节码指令时,而近乎没有经过初始化。生成当下4单令的科普景象:使用new关键字实例化对象、读取或设置类的静态字段(使用final关键字修饰除外)、调用类的静态方法

  • 利用java.lang.reflect进行映调用,而近乎没有经过初始化。

  • 当年始化一个看似时,发现该父类还没有受初始化,则需要先初始化其父类

  • 当虚拟机启动时,用户用指定一个主类(包含main方法),虚拟机会初始化这个主类。

回顾第1条,为甚final关键字修饰的静态成员变量会不同呢?回顾Class文件结构常量池知识,如果是final关键字修饰的static变量,它的价使用ConstantValue进行初始化,非final修饰的static变量在
clinit 方法吃初始化。

以上4种植现象给虚拟机称为有还只有的会触发类初始化的现象,这4种植现象为称对类的积极引用。除此之外的具有场景都非会见触发类的初始化,被号称被动引用。

public class SuperClass {
    /*
     * 被动引用父类静态变量,不会初始化子类
     */
    static{
        System.out.println("super class init");
    }
    public static int value = 123;
}

public class SubClass extends SuperClass{
  static{
      System.out.println("sub class init");
  }
}

public class ConstClass {
  static{
      System.out.println("const class init");
  }
  public static final String HELLO = "hello world";
}

/*
 * 被动引用父类静态变量,不会初始化子类
 */
public static void invokeSuperStatic(){
    System.out.println(SubClass.value);
}

/*
 * 通过数组定义来引用类,不会触发类的初始化
 */
public static void accessByArray(){
    SuperClass[] array = new SuperClass[10];
}

/*
 * 访问final static变量,不会初始化类
 */
public static void accessFinalField(){
    System.out.println(ConstClass.HELLO);
}

要是齐代码,一共对应了3种被动引用方式。

  • 引用父类的静态变量,不会见初始化子类
  • 通过数组定义来引用类,不见面触发类的初始化。此时莫初始化 SuperClass
    类,但初始化了 [Lcom.okunu.jvm.init
    这个看似,它由字节码指令newarray触发,且实现了多次组的 length
    等方式。数组也是目标,是Java自动生成的目标,所以她并无会见失掉初始化数组中的元素类。
  • static
    final类型的常量,在编译阶段曾将这常量存储到了NotInit类的常量池了,对常量
    HELLO
    的援已经转向对自我常量池的援了,所以不会见初始化ConstClass类了

仿佛加载过程

类似加载过程分成 加载、验证、准备、解析、初始化、使用、卸载
7单等级,本文中教授前面5独号

1、加载

加载阶段要做以下工作:

  • 经过类似的全限定名获取类的二进制文件流
  • 以许节约流所代表的静态存储结构转变吗方法区的周转时数据结构
  • 每当积中生成此类的 java.lang.Class 对象,作为方法区这些数据的造访入口

加载阶段是支付可控性最强之阶段,比如说开发好运用从定义类加载器去加载某个类,类的来呢可以是jar包、class文件、甚至是网络流。

2、验证

说明是连续过程的第1步,它是为了保Class文件的字节流符合虚拟机的正统。

加载阶段加载Class文件,并未规定Class文件的自,如果愿意,甚至好手动编写Class文件,但诸如此类的Class文件来或未适合虚拟机规范,所以要征。它最主要进行如下方面证实:

  • 文件格式验证
  • 伯数据证明
  • 许节码验证
  • 记引用验证

3、准备

未雨绸缪阶段是吗接近变量正式分配内存并赋默认值的级差,这些内存都当方法区内分配。

起点儿单点需要强调:

  • 一味为接近变量分配内存,即是为static变量分配内存,一般成员变量是于近似为实例化时分配内存
  • 独见面吧static变量赋默认值

借设有如下static变量

  public static int value = 123

那么准备阶段,value的值会变成0,而无是123。而将value赋值为123的putstatic指令是当次为编译后,存放于
clinit 方法吃,所以value被赋值为123 发生在 初始化 阶段,即第5独号。

假设是final修饰的static变量呢?如果字段的字段属性表中在ConstantValue属性,那么准备阶段,变量就会吃初始化为ConstantValue存储的价值。

比方上述变量为

  public static final int value = 123

编译时javac会为value生成ConstantValue属性,准备等value的价就会见化123。

4、解析

解析阶段是将常量池中的标志引用替换为直接引用的长河。

标记引用是借助字面量,能任歧义地稳定到对象便吓,它跟虚拟机的内存布局无关。

一直引用,是乘好直接看到目标的指针或句柄,与内存布局相关的。

浅析主要针对类或接口、字段、类措施、接口方法4类符号引用。

5、初始化

初始化是看似加载过程的末尾一步,除了加载过程,开发可利用由定义类加载器参与他,其它过程全是虚拟机自己决定着。初始化阶段,才真正开始实践Java程序代码。

准备等,为static变量分配内存并赋默认值,而初始化阶段则会履clinit方法,为static变量分配代码中所指定的价。

  • <clinit>方法是虚拟机自动生成的,编译器收集类中类变量的赋值语句、static静态语句块合并有<clinit>方法。收集之相继是代码顺序

  • <clinit>方法以及 init
    方法不同,它不需要展示调用父类<clinit>方法,因为虚拟机保证在调用子类<clinit>方法前,父类<clinit>方法就推行了。

  • 因为父类的<clinit>方法先实行,所以父类静态语句块要先为子类的静态语句块。

  • <clinit>方法并无是得的,如果代码中没定义静态变量或者静态语句块,则从未<clinit>方法。

恍如加载器

接近加载器的图就是实现加载阶段的任务,通过一个好像的全限定名获取Class文件的亚前行制字节流。

要是同一个近乎由个别独例外之类似加载器加载,得到两单实例,那么就半只实例在虚拟机看来,并无平等种档次。

看似加载器可以分成三近似:

  • 启航类加载器(Bootstrap ClassLoader),负责用存放在于 JAVA_HOME\lib
    目录下同时是虚构机识别的类库加载到虚拟机内存当中来

  • 扩大类加载器(Extension ClassLoader),它肩负加载存放于
    JAVA_HOME\lib\ext 目录下的类库

  • 用户程序类加载器(Application
    ClassLoader),它是默认的类似加载器,负责加载ClassPath上点名的类库,如果有类没有异常指定类加载器,就应用她。

咱们的应用程序都是由当时三好像类加载器完成接近加载的,如果需要,我们尚足以兑现由定义的接近加载器完成接近加载。它们的干如下图所示:

落得图所展示的接近加载器关系,就叫类加载器的爹娘委派模型。除了顶层的开行类加载器,其它的好像加载器都生投机的父类加载器,但它们不是由此连续实现的,而是通过结合实现的。

父母委派模型的做事进程是:如果一个接近加载器收到了仿佛加载请求,会先请求自己的父类加载器去加载,如果父类无法加载该类,子类才见面尝试自己失去加载。

protected Class<?> loadClass(String name, boolean resolve){
    Class c = findLoadedClass(name);
    if (c == null) {
        if (parent != null) {
            //使用父加载器加载此类
            c = parent.loadClass(name, false);
        } 
        if (c == null) {
            // 如果父加载器没有成功加载,则自己尝试加载
            c = findClass(name);
        }
    }
    return c;
}

查ClassLoader类的loadClass方法,双亲委派模型从上述代码中贯彻。

下对亲委派模型,Java类和它们的类加载器一起有了一致栽含有优先级的层次关系。例如Object类,无论哪个类加载器来加载它,最后都要寄启动类加载器来加载Object,这便管了Object在先后的依次类加载器环境受到都是跟一个接近。Java的基本类,基础行为不可不吃包,不克于歪曲,这虽是父母亲委派模型的意思所在。

相关文章