Java类加载过程
1. 加载(loading)
主要分为三个步骤:
- 通过一个类的全限定名来获取定义此类的二进制字节流。
- 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
- 在Java堆中生成一个代表这个列的java.lang.Class对象,作为方法区这些数据的访问入口。
2. 链接(linking)
2.1 验证(verification)
主要包括:
- 文件格式验证
- 元数据验证(语义校验,保证符合Java语义规范)
- 字节码验证
- 符号引用验证(检查类,字段,方法是否存在,访问限制等等)
2.2 准备(preparation)
为类的静态变量分配内存和初始化。这些内存在方法区分配,初始化的值“通常情况”下是数据类型的零值。假设一个类变量定义为:
public static int value = 123;
那么value将被初始化为0,上面的赋值操作将在<cinit>()方法中进行。
假设一个类变量定义为(常量):
public static final int value = 123;
那么value将被初始化为123。
2.3 符号解析(resolution of Symbolic References)
一个Java类中通常会包含对其他类或接口的引用,解析过程就是确保这些被引用的类能被正确的找到。解析的过程可能会导致其他Java类被加载。被引用类的加载由引用类的类加载器执行。
不同的JVM实现可能选择不同的解析策略。一种做法是在链接的时候,就递归的把所有依赖的形式引用都进行解析。而另外的做法则可能是只在一个形式引用真正需要的时候才进行解析。
3. 初始化(initialization)
虚拟机规范严格规定有且只有四种情况必须对类进行初始化:
遇到new,getstatic,putstatic,或invokestatic这4条字节码指令的时候。
使用java.lang.reflect包的方法对类进行反射调用的时候。
当初始化一个类的时候,如果其父类还没有进行过初始化,则需要先触发其父类的初始化。
虚拟机启动时,用户需要指定一个要执行的类,虚拟机将初始化这个类。
初始化是执行类构造器<cinit>()的过程。
<cinit>()方法由静态变量赋值操作和静态语句块合并而成,合成顺序按照文件中出现的顺序。编译器执行该操作。
虚拟机保证初始化过程在多线程中被正确的加锁和同步。
虚拟机保证子类的<cinit>()执行之前,父类的<cinit>()已经执行完毕。
执行接口的<cinit>()方法不需要先执行父接口的<cinit>()方法,接口的实现类在初始化时也一样不会先执行接口的<cinit>()方法。