虚拟机类的加载机制

一. 类加载的时机

     类从被加载到虚拟机内存开始,到卸载出内存为止,它的整个生命周期包括:加载、验证、准备、解析、初始化、使用和卸载7个阶段。其中加载、验证、准备、初始化和卸载这五个阶段的顺序是确定的,类的加载机制过程必须按照这个顺序,按部就班的开始,而解析阶段则不一定:它在某些情况下可以在初始化阶段后再开始为了直接Java的动态绑定。
     加载开始的时机,Java虚拟机规范中并没有进行强制约束,可以交个虚拟机的具体实现自己把握。
     类的初始化阶段,Java虚拟机规范则是严格规定了有且只有5种情况必须立即对类进行“初始化”(而加载、验证、准备已经在此之前开始):
    1)遇到new、getstatic、putstatic或invokstatic这4条字节码指令时,如果类没有进行过初始化,则需要先触发其初始化。生成这4条指示的最常见的Java代码场景是:使用new关键字实例化对象的时候、读取或设置一个类的静态字段(被final修饰、已在编译期把结果放入常量池的静态字段除外)的时候,以及调用一个类的静态方法的时候。
     2)使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有进行过初始化,则需要先触发其初始化。
     3)当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发父类的初始化。
     4)当虚拟机启动时,用户需要指定一个要执行的主类(包含main方法的类),虚拟机会先初始化这个主类。
     5)当使用JDK 1.7 的动态语言支持时,如果一个 java.lang.invoke.MethodHandle 实例最后的解析结果是REF_getStatic、REF_putStatic、REF_invokeStatic 的方法句柄,并且这个方法句柄所对应的类没有进行初始化,则需要先触发其初始化。
     这 5 种场景中的行为被称为对一个类进行主动引用。除此以外,所有引用类的方法都不会触发初始化,称为被动引用
     

/**
 * 被动使用类字段演示一:
 * 通过子类引用父类的静态子段,不会导致子类的初始化
 * */

public class SuperClass {

    static {
        System.out.println(" SuperClass init");
    }

    public static int value = 123;
}

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

/**
 * 非主动使用类字段的演示
 */
public class NotInitialization {
    public static void main(String[] args) {
        System.out.println(SubClass.value); // SuperClass init
    }
}

       对于静态字段,只有直接定义这个字段的类才会被初始化,因此通过其子类来引用父类中定义的静态字段,只会触发父类的初始化而不会触发子类的初始化。至于是否要触发子类的加载和验证,在虚拟机规范中并未明确规定,这点取决于虚拟机的具体实现。

 

/**
 * 被动使用类字段演示二:
 * 通过数组定义来引用类,不会触发此类的初始化
 */
public class NotInitialization {
    public static void main(String[] args) {
        SuperClass[] superClass = new SuperClass[10];  
        // 没有输出 SuperClass init,没有触发 SuperClass的初始化。
    }
}

       没有输出 SuperClass init,说明没有触发 SuperClass的初始化阶段。但是代码中触发了一个名为 "[Lorg.fenixsoft.classloading.SuperClass" 的类的初始化阶段,它是由虚拟机自动生成的,直接继承于 java.lang.Object 的子类,创建动作由字节码指令 newarray 触发。

 

/**
 * 被动使用类字段演示三:
 * 常量在编译阶段会存入调用类的常量池中,本质上并没有直接引用到定义常量的类,
 * 因此不会触发定义常量的类的初始化。
 */
class ConstClass {

    static {
        System.out.println(" ConstClass init"); 
    }

    public static final String HELLOWORLD = "hello world";
}

/**
 * 非主动使用类字段演示
 */
public class NotInitialization {

    public static void main(String[] args) {
        System.out.println(ConstClass.HELLOWORLD); // hello world
    }

}

    上述代码运行之后,没有输出“ConstClass init ”,这是因为在编译阶段通过常量传播优化,已经将此常量的值"hello world" 存储到 NotInitialization 类的常量池中, 以后NotInitialization 对常量ConstClass.HELLOWORLD 的引用实际都被转化为  NotInitialization 类的对自身常量池的引用了。这两个类在编译成Class之后就不存在任何联系了。

二. 类加载的过程
     “加载”是"类加载"过程的一个阶段。在加载阶段,虚拟机需要完成以下 3 个事情:
        1)通过一个类的全限定名来获取定义此类的二进制字节流。
        2)将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
        3)在内存中生成一个代表这个类的 java.lang.Class 对象,作为方法区的这个类的各种数据的访问入口。
   加载阶段完成后,虚拟机外部的二进制字节流就按照虚拟机所需的格式存储在方法区之中,方法区中的数据存储格式有村级实现自行定义,然后内存中实例化一个 java.lang.Class 类的对象,作为程序访问方法区中的这些类型数据的外部接口。

验证
       验证是连接阶段的第一步,目的是为了确保 Class 文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害到虚拟机自身的安全。验证阶段是非常重要的,这个阶段是否严谨,直接决定 Java虚拟机是否能承受恶意代码的攻击,从执行性能角度上讲,验证阶段的工作量在虚拟机的类加载子系统中又占了相当大的一部分。验证阶段大致会完成下面四个阶段的校验动作:文件格式验证、元数据验证、字节码验证、符号引用验证。
       1.文件格式验证
        第一阶段要验证字节流是否符合 Class 文件格式的规范,并且能被当前版本的虚拟机处理。该阶段的主要目的保证输入的字节流能正确的解析并存储于方法区之内,格式上符合描述一个 Java 类型信息的要求。 这个阶段的验证是基于二进制字节流进行的,只有通过了这个阶段的验证后字节流才会进入内存的方法区中进行存储,所以后面的 3 个验证阶段全部是基于方法区的存储结构进行的,不会再直接操作字节流
        2.元数据验证
        第二阶段是对字节码描述的信息进行语义分析,保证其描述的信息符合 JAVA 语言规范的要求。主要目的是对类的元数据信息进行语义校验,保证不存在不合格Java语言规范的元数据信息。例:类中的字段、方法是否与父类产生矛盾。父类是否继承了不允许被继承的类(被final修饰的类)。
        3.字节码验证
         第三阶段是整个验证过程中最复杂的一个阶段,主要目的是通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的,主要对类的方法体做校验分析。如果一个类方法体的字节码没有通过字节码验证,那肯定是有问题的;但如果一个方法体通过了字节码验证,也不能说明其一定就是安全的。
        4.符号引用验证
        最后一个阶段的校验发生在虚拟机将符号引用转化为直接引用的时候,这个转化动作将在连接的第三阶段——解析阶段中发生。符号引用验证可以看做是对类自身以外的信息(常量池的各种符号的引用)进行匹配性验证。符号解析的主要目的是解析动作能正常执行,如果无法通过符号引用验证,会抛出一个异常。
      对于虚拟机的类加载机制来说,验证阶段是一个非常重要的、但不是一定必要(因为对程序运行期没有影响)的阶段。如果所运行的全部代码都已经被反复使用和验证或了,可以通过配置 -Xverify:none参数来关闭大部分的类验证措施,以缩短虚拟机类加载的时间。

《深入理解java虚拟机》
  

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值