一)Example: 多个类的初始化

package me;
class A {
    static int a = 1;
    static {
        a = 2;
        System.out.print("A init");
    }
    static int getA(){
        return a;
    }
}
class B extends A{
    static int b = 1;
    static {
        b = 2;
        System.out.print("B init");
    }
}
class Main {
    static public void main(String... args){
        System.out.print(A.a);//0
        System.out.print(B.a);//1
        System.out.print(B.getA());//2
        System.out.print(B.b);//3
    }
}

在进入正文前,首先根据上面的例子试图猜测:为了Main类中的main()函数能够运行,JVM需要做哪些前置工作呢?这些工作是以什么顺序展开的呢?
为了更好的描述多个类的情形,可以先试图描述一个更简单的例子:把行1和行2和行3注释掉.

二)类:从0到1

一句话: 把类从介质中复制到JVM方法区,通过各种规则验证和符号解析,最后根据程序员的逻辑赋值或其他语句完成初始化

Step1.类的加载(Loading)

加载是把存储类的实体从各类介质(文件/网络/数据库/内存中实时生成等)加载到JVM的方法区中的过程,主要包含2步:

  1. 根据类的全限定名"me.B"通过 _ApplicationClassLoader_(或者是自己定义的ClassLoader)获得B类的二进制流
  2. 把流读入内存中,并转化为JVM规定的方法区结构,生成对应的java.lang.Class对象

现在,你可以在程序中访问到这个类~(≧▽≦)/~啦

Step2.类的连接(Linking)

JVM中的连接细分为独立的3步:验证/准备/解析
验证和准备的开始是有严格的顺序的,但是JVM可以自由选择解析发生的时机,甚至放到初始化之后

连接可以理解为把源代码转化为可执行程序的过程,当然不同于C/C++连接生成的.exe程序,JVM中的可执行程序一般是字节码(bytecode)程序

Step3.验证(Validation)

验证过程较为复杂,JVM主要验证了class文件格式/java语义限制/java程序逻辑正确性和安全性,其主要技术为静态的字节码分析,所以不能保证100%的可靠

Step4.准备(Preparation)

me.B类中的静态变量int b在堆中分配内存,并设置其初始值0

Step5.解析(Resolution)

简单地说,是将符号引用转化为直接引用的过程,所有符号引用都必须转化为直接引用

符号引用: 如方法名/变量名/类等等的符号
直接引用: 直接指向一块内存区域

Step6.类的初始化(Initialization)

在初始化之前,程序已经可以在内存中访问到me.B类和其类变量了

初始化所做的事情也很简单:JVM从上到下收集类变量赋值语句和类静态初始化块中的语句,把它放到JVM生成的<clinit>()方法中,再执行之

对于初始化的时机,JVM有严格的规范:只有在主动引用时才会触发类的初始化

主动引用有5种情况:大致可以理解为以下几种

  1. 以各种方式读静态变量/写静态变量/触发静态方法时(包括出现相应的字节码指令/反射/invokedynamic)类却没有初始化
  2. 包含入口方法main()的类
  3. 初始化子类时父类还没初始化
    其他情况都是被动引用

可以看到,me.B<clinit>()方法中执行了下列代码:

b = 1;
b = 2;
System.out.print("B init");

三)类:从1到n再到0

类的使用(Using)

类的实例化过程,包括new/反射等方法

类的卸载(Unloading)

当程序中无论什么方法都无法引用该类时,类从方法区被回收,大致可以理解为

  1. 类的所有实例被回收
  2. 类的ClassLoader被回收
  3. 没有地方引用Class对象,也无法通过反射访问

四)Example:执行过程简述

回到最初的例子,可以列出预计的执行顺序为:

  1. 初始化me.Main
  2. 加载me.B
  3. 初始化me.A: 输出"A init"
  4. System.out.print(A.a);:输出2
  5. System.out.print(B.a);:输出2
  6. System.out.print(B.getA());:输出2
  7. 初始化me.B:输出"B init"
  8. System.out.print(B.b);:输出2

实际执行结果验证:

执行结果


Jiadong
454 声望42 粉丝

秋名山撒欢,排水沟过弯