java查看类的生命周期 java类加载的生命周期_类变量

#一、类的生命周期 类从被加载到虚拟机内存开始,到卸载出内存为止,整个生命周期包括:加载、验证、准备、解析、初始化、使用和卸载七个阶段。 其中加载、验证、准备、初始化、和卸载这5个阶段的顺序是确定的。而解析阶段不一定:它在某些情况下可以在初始化阶段之后再开始,这是为了支持Java的运行时绑定。
关于初始化:JVM规范明确规定,有且只有5中情况必须执行对类的初始化(加载、验证、准备自然再此之前要发生):

  1. 遇到new、getstatic、putstatic、invokestatic,如果类没有初始化,则必须初始化,这几条指令分别是指:new新对象、读取静态变量、设置静态变量,调用静态函数。
  2. 使用java.lang.reflect包的方法对类进行反射调用时,如果类没初始化,则需要初始化
  3. 当初始化一个类时,如果发现父类没有初始化,则需要先触发父类初始化。
  4. 当虚拟机启动时,用户需要制定一个执行的主类(包含main函数的类),虚拟机会先初始化这个类。
  5. 但是用JDK1.7启的动态语言支持时,如果一个MethodHandle实例最后解析的结果是REF_getStatic、REF_putStatic、Ref_invokeStatic的方法句柄时,并且这个方法句柄所对应的类没有进行初始化,则要先触发其初始化。

注意:通过子类来引用父类的静态字段,不会导致子类初始化

public class SuperClass{
    public static int value = 123;
    static{
        System.out.printLn("SuperClass init!");
    }
}

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

public class Test{
    public static void main(String[] args){
        System.out.println(SubClass.value);
    }
}

最后只会打印:SuperClass init!
常量会在编译阶段存入调用者的常量池,本质上并没有直接引用到定义常量的类,因此不会触发定义常量的类初始化

public class ConstClass{
    public static final String HELLO_WORLD = "hello world";
    static {
        System.out.println("ConstClass init!");
    }
}

public class Test{
    public static void main(String[] args){
        System.out.print(ConstClass.HELLO_WORLD);
    }
}

最后结果不会出现“ConstClass init!”,只会打印:hello world!
加载

加载过程主要做以下3件事

  1. 通过一个类的全限定名称来获取此类的二进制流
  2. 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
  3. 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据访问入口。

验证

这个阶段主要是为了确保Class文件字节流中包含信息符合当前虚拟机的要求,并且不会出现危害虚拟机自身的安全。

准备

准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些变量所使用的内存都在方法区中分配。首先,这个时候分配内存仅仅包括类变量(被static修饰的变量),而不包括实例变量。实例变量会在对象实例化时随着对象一起分配在java堆中。其次这里所说的初始值“通常情况下”是数据类型的零值,假设一个类变量定义为

public static int value = 123;

那变量value在准备阶段后的初始值是0,而不是123,因为还没有执行任何Java方法,而把value赋值为123是在程序编译后,存放在类构造函数< clinit >()方法中。
解析

解析阶段是把虚拟机中常量池的符号引用替换为直接引用的过程。

初始化

类初始化时类加载的最后一步,前面类加载过程中,除了加载阶段用户可以通过自定义类加载器参与以外,其余动作都是虚拟机主导和控制。到了初始化阶段,才是真正执行类中定义Java程序代码。

准备阶段中,变量已经赋过一次系统要求的初始值,而在初始化阶段,根据程序员通过程序制定的主观计划初始化类变量。初始化过程其实是执行类构造器< clinit >()方法的过程。

< clinit >()方法是由编译器自动收集类中所有类变量的赋值动作和静态语句块中的语句合并产生的。收集的顺序是按照语句在源文件中出现的顺序。静态语句块中只能访问定义在静态语句块之前的变量,定义在它之后的变量可以赋值,但不能访问。如下所示:

public class Test {
    static{
        i = 0;//給变量赋值,可以通过编译
        System.out.print(i);//这句编译器会提示:“非法向前引用”
    }
    static int i = 1;
}

< clinit >()方法与类构造函数(或者说实例构造器< init >())不同,他不需要显式地调用父类构造器,虚拟机会保证子类的< clinit >()方法执行之前,父类的< clinit >()已经执行完毕。

卸载
当代表一个类的Class对象不再被引用,即不可触及时,Class对象就会结束生命周期,该类在方法区内的数据也会被卸载,从而结束类的生命周期。
由Java虚拟机自带的类加载器所加载的类,在虚拟机的生命周期中,始终不会被卸载。Java虚拟机自带的类加载器包括根类加载器、扩展类加载器和系统类加载器。

#二、加载机制
Java类的初始化

本阶段负责为类变量赋正确的初始值。(类变量即静态变量)
Java编译器把所有的类变量初始化语句和静态初始化器通通收集到<clinit>方法中,该方法只能被JVM调用,专门承担初始化工作。 初始化一个类必须保证其直接超类已被初始化。
并非所有类都拥有<clinit>()方法,以下类不会拥有<clinit>方法:

  1. 该类既没有声明任何类变量,也没有静态初始化语句。
  2. 该类声明了类变量,但没有使用类变量初始化语句或静态初始化语句初始化。
  3. 该类只包含静态final变量的类变量初始化语句,并且类变量初始化语句是常量表达式。

Java类初始化的时机
规范定义类的初始化时机为“initialize on first active use”,即“在首次主动使用时初始化”。装载和链接在初始化之前就要完成。
首次主动使用的情形:

  1. 创建类的新实例--new,反射,克隆或反序列化;
  2. 调用类的静态方法;
  3. 操作类和接口的静态字段;(final字段除外)
  4. 调用Java的特定的反射方法;
  5. 初始化一个类的子类;
  6. 指定一个类作为Java虚拟机启动时的初始化类(含有main方法的启动类)。

除了以上6种情形,java中类的其他使用方式都是被动使用,不会导致类的初始化。

Java对象初始化
编译器为每个类生成至少一个实例初始化方法,即<init>()方法。此方法与源程序里的每个构造方法对应。如果类没有声明构造方法,则生成一个默认构造方法,该方法仅调用父类的默认构造方法,同时生成与该默认构造方法对应的<init>()方法。 <init>()方法内容大概为:

  1. 调用另一个<init>()方法(本类的另外一个<init>()方法或父类的<init>()方法);
  2. 初始化实例变量;
  3. 与其对应的构造方法内的字节码

Java对象初始化的时机
对象初始化又称为对象实例化。Java对象在其被创建时初始化。
有两种方式创建Java对象:一种是显示对象创建,通过new关键字来调用一个类的构造函数,通过构造函数创建一个对象。