虚拟机类加载机制

来源:互联网 发布:单片机中断编写生日歌 编辑:程序博客网 时间:2024/05/17 07:51
类型的加载、连接和初始化过程都是在程序运行期间完成的。


其中类加载的过程包括了加载、验证、准备、解析、初始化五个阶段。在这五个阶段中,加载、验证、准备和初始化这四个阶段发生的顺序是确定的,而解析阶段则不一定,它在某些情况下可以在初始化阶段之后开始,这是为了支持 Java 语言的运行时绑定(也成为动态绑定或晚期绑定)。另外注意这里的几个阶段是按顺序开始,而不是按顺序进行或完成,因为这些阶段通常都是互相交叉地混合进行的,通常在一个阶段执行的过程中调用或激活另一个阶段。

这里简要说明下 Java 中的绑定:绑定指的是把一个方法的调用与方法所在的类(方法主体)关联起来,对 Java 来说,绑定分为静态绑定和动态绑定:

  • 静态绑定:即前期绑定。在程序执行前方法已经被绑定,此时由编译器或其它连接程序实现。针对 Java,简单的可以理解为程序编译期的绑定。Java 当中的方法只有 final,static,private 和构造方法是前期绑定的。
  • 动态绑定:即晚期绑定,也叫运行时绑定。在运行时根据具体对象的类型进行绑定。在 Java 中,几乎所有的方法都是后期绑定的。

加载

加载时类加载过程的第一个阶段,在加载阶段,虚拟机需要完成以下三件事情:

  • 通过一个类的全限定名来获取其定义的二进制字节流,可以从class文件或者jar或者网络获取。
  • 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
  • 在内存中(并未明确规定是在java堆中,对于hotspot虚拟机而言,存放在方法区里)生成一个代表这个类的 java.lang.Class 对象,作为对方法区中这些数据的访问入口。

由它的类加载器和这个类本身一同确定其在就 Java 虚拟机中的唯一性,也就是说,即使两个类来源于同一个 Class 文件,只要加载它们的类加载器不同,那这两个类就必定不相等。这里的“相等”包括了代表类的 Class 对象的 equals()、isAssignableFrom()、isInstance()等方法的返回结果,也包括了使用 instanceof 关键字对对象所属关系的判定结果。


JVM 的类加载是通过 ClassLoader 及其子类来完成的,类的层次关系和加载顺序可以由下图来描述:





类加载器的双亲委派模型


如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把请求委托给父加载器去完成,依次向上


使用双亲委派模型来组织类加载器之间的关系,有一个很明显的好处,就是 Java 类随着它的类加载器(说白了,就是它所在的目录)一起具备了一种带有优先级的层次关系,这对于保证 Java 程序的稳定运作很重要。例如,类java.lang.Object 类存放在JDK\jre\lib下的 rt.jar 之中,因此无论是哪个类加载器要加载此类,最终都会委派给启动类加载器进行加载,这边保证了 Object 类在程序中的各种类加载器中都是同一个类。

验证
分为:
文件格式验证:只有通过这个验证,字节流才会进入内存的方法区中进行存储,所以后面3个验证阶段都是基于方法区的存储结构进行的,不会再直接操作字节流;
元数据验证:语义分析
1.这个类是否有父类;
2.是否继承了不被允许的类(比如final类);
3.如果不是抽象类,是否实现了父类或者接口要求实现的所有方法;
4.类中字段、方法是否与父类产生矛盾(例如覆盖了父类的final字段,或者不符合规则的方法重载)

字节码验证:
符号引用验证:
发生在虚拟机将符号引用转化为直接引用的时候,在解析阶段进行;
如果无法通过符合引用验证,将抛出异常 NoSuchMethodError等;

准备
准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些内存都将在方法区中分配。对于该阶段有以下几点需要注意:

  • 这时候进行内存分配的仅包括类变量(static),而不包括实例变量,实例变量会在对象实例化时随着对象一块分配在 Java 堆中。
  • 这里所设置的初始值通常情况下是数据类型默认的零值(如 0、0L、null、false 等),而不是被在 Java 代码中被显式地赋予的值。

假设一个类变量的定义为:

public static int value = 3;
那么变量 value 在准备阶段过后的初始值为 0,而不是 3,因为这时候尚未开始执行任何 Java 方法,而把 value 赋值为 3 的 putstatic 指令是在程序编译后,存放于类构造器 ()方法之中的,所以把 value 赋值为 3 的动作将在初始化阶段才会执行。


如果类字段的字段属性表中存在 ConstantValue 属性,即同时被 final 和 static 修饰,那么在准备阶段变量 value 就会被初始化为 ConstValue 属性所指定的值。

假设上面的类变量 value 被定义为:

public static final int value = 3;
编译时 Javac 将会为 value 生成 ConstantValue 属性,在准备阶段虚拟机就会根据 ConstantValue 的设置将 value 赋值为 3。