Java虚拟机类加载机制

时间:2022-12-21 09:47:13

当程序需要某个类的时候,如果改类还没有被加载到方法区内存中,则系统会通过加载,链接(验证,准备,解析),初始化三步来实现对这个类的初始化。

加载:

  1.通过一个类的权限定名来获取定义此类的二进制字节流。

  2.将这个字节流所带边的静态存储结构转化为方法区的运行时数据结构。

  3.在方法区生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。

 加载完成后,虚拟机外部的二进制字节流就按照虚拟机所需要的格式存储在方法区,方法区中的数据存储格式由虚拟机实现自定义,虚拟机规范并未此区域的具体数据结构。然后在内存中实例化一共java.lang.Class对象(并没有规定是在java堆中,对于HopSpot虚拟机而言,Class对象比较特殊,它虽然是对象,但是存放在方法区里面),这个对象将作为程序访问方法区中这些类型数据的外部接口。

连接:

  1.验证 这个阶段的目的是为了确保Class文件的字节流中包含的信息(方法区信息)符合当前虚拟机的要求,并不会危害虚拟机自身的安全。

  2.准备 准备阶段是正式为类变量(静态成员变量)分配内存并设置类变量默认初始化值的阶段,这些变量所使用的内存都将在方法区中进行分配。这个阶段中有两个容易产生混淆的概念需要强调一下。首先,这个时候进行内存分配的仅包含类信息(被static)修饰的变量,而不包含实例变量(非静态成员变量),实例变量将会在对象实例化时随着对象一起分配在Java堆中。其次,这里所说的初始化值时数据类型的零值,假设一个类变量的定义为:

public static int value=123;

 那变量 value 在准备阶段过后的初始化值为0 而不是123,因为这个时候尚未开始执行任何Java方法,表达式等,而把value赋值为123的putstatic指令时程序被编译后,存放于类构造器<clinit>()方法中,所以把value赋值为123的动作将在初始化阶段才会执行。

解析

解析阶段就是虚拟机将常量池的符合引用替换为直接引用的过程,符号引用在Class文件中它以CONSTANT_Class_info、CONSTANT_Fieldref_info、CONTSTANT_Methodref_info等类型出现。

    a.符号引用:符号引用以一组符号来描述所引用的目标,符号可以时任何形式的字面量,只要使用时能无歧义地定位到目标即可。符号引用和虚拟机实现的内存布局无关,引用的目标并不一定已经加载到内存中。各种虚拟机实现的内存布局可以各不相同,但是他们能够接受的符号引用必须是一致的,因为符号引用的字面量形式明确定义在Java虚拟机规范的Class文件中。

    b.直接引用:直接引用可以是直接指向目标的指针、相对偏移量或是一共能间接定位到目标的句柄。直接引用是和虚拟机实现的内存布局相关的,同一个符号引用在不同虚拟机实例上翻译出来的直接引用一般不会相同。如果有了直接引用,那引用的目标必定已经在内存中存在。

   注意:

      Class对象只是对.class文件的一共描述性信息,并没有存储任何的值。

      Class对象是对操作方法区类的数据结构的接口

      方法区类的数据结构已经初始化了静态成员变量和执行了static语句块后的数据结果。

      下次修改static成员变量后,修改结果会直接反应到方法区里面的数据结构中。

      任何类被使用时系统都会建立一共Class对象,并且是唯一的一个。

初始化

    细节上:执行类变量的赋值语句和静态初始化语句,给静态成员赋值。

    具体:所有的类变量初始化语句和类型的静态初始化器都被Java编译器收集在一起,放到一个特殊方法中,对于类和接口来说,这就是类的初始化方法和接口的初始化方法,在类和接口中的Java class文件中,这个方法被成为“<clinit>”。通常的Java程序是无法调用这个方法的,只能被虚拟机调用,专门把类型的静态变量设置为它的正确初始化值。即执行赋值语句。如果当前类存在直接超类的话,就先初始化超类,第一个初始化的永远是Object,然后就是被主动使用的累的继承树上的所有的类。注意,在初始化阶段会初始化静态代码块。但是不会初始化构造方法,除了创建类的实例、使用java.exe命令运行某个主类的这几个时机(java.exe运行,本质上就是调用main方法,所以必须要有main方法才行)。

   虚拟机规范严格规定了下面几种情况必须立即执行对类的初始化(而加载、验证、准备自然需要在此之前开始):

     1.创建类的实例

     2.访问类的静态变量(注意:当访问类的静态并且final修饰的变量时,不会触发类的初始化。),或者为静态变量赋值。

     3.调用类的静态方法(注意:调用静态且final的成员方法时,会触发类的初始化!)

        4.使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有进行初始化,则需要先触发其初始化。如:Class.forName("bacejava.Langx");

     5.通过类名.class得到Class文件对象并不会触发类的加载。

     6.初始化某个类的子类

     7.直接使用java.exe命令来运行某个主类(java.exe运行,本质上就是调用main方法,所以必须要有main方法才行)。

     8.通过子类调用父类的静态成员时,只会初始化父类而不会初始化子类。因为没有调用子类的相关静态成员,这也叫能不加载就不加载原则。

     9.调用静态成员时,会加载静态成员所在的类及其父类。

     10.类的加载成功后,即静态成员都被加载后,是不会再加载第二次的,只有非静态成员,如非静态成员变量、非静态代码块、非静态方法(不掉用不加载)、构造方法都会被多次实例化的时候多次加载。

     11.如果静态属性有final修饰时,则不会加载,当成常量使用,例:public static final int a = 123;但是静态属性 有final修饰 时也有情况会被加载,public static final int a=getNum();这样也会被加载。getNum()是静态方法,并且不管这个静态方法是子类的还是父类的;

     12.在上面触发类被初始化的情况称为对类的主动引用,除此之外,那些引用类的方式没有触发初始化的叫做被动引用