类加载的时机

类从被加载到虚拟机内存开始,到卸载出内存为止,它的整个生命周期包括:加载、验证、准备、解析、初始化、使用和卸载7个阶段。

其中验证、准备、解析3个部分统称为连接。加载、验证、准备、初始化和卸载这5个阶段的顺序是确定的。

有且只有五种情况必须立即对类进行“初始化”:

  1. 遇到new、getstatic、putstatic、invokestatic这4条字节码指令时,如果类没有进行过初始化,则需要先触发其初始化。生成这4条指令的最常见的Java代码场景是:使用new关键字实例化对象的时候、读取或设置一个类的静态字段(被final修饰、已在编译期把结果放入常量池的静态字段除外)的时候,以及调用一个类的静态方法的时候。
  2. 使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有进行过初始化,则需要先触发其初始化。
  3. 当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。
  4. 当虚拟机启动时,用户需要指定一个要执行的主类(包含main方法的那个类),虚拟机会先初始化这个主类。
  5. 当使用JDK1.7的动态语言支持时,如果一个java.lang.MethodHandle实例最后的解析结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先触发其初始化。

这五种场景中的行为称为对一个类进行主动引用。除此之外,所有引用类的方式都不会触发初始化,称为被动引用。

被动引用例子之一:

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!”,而不会输出“SubClass init!”。对于静态字段,只有直接定义这个字段的类才会被初始化

被动引用例子之二:

public class NotInitialization {

    public static void main(String[] args) {
        SuperClass[] sc = new SuperClass[10];
    }
}

没有输出“SuperClass init!”。通过数组定义来引用类,不会触发此类的初始化。

被动引用例子之三(常量传播优化):

public class ConstClass {

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

    public static final String HELLOWORLD = "hello world";

    public static void test(){
        System.out.println("test.");
    }

}
public class NotInitialization {

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

控制台输出:

hello world

javap 查看编译后的NotInitialization.class

public class com.fcs.jvms.NotInitialization
  SourceFile: "NotInitialization.java"
  minor version: 0
  major version: 51
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #6.#20         //  java/lang/Object."<init>":()V
   #2 = Fieldref           #21.#22        //  java/lang/System.out:Ljava/io/PrintStream;
   #3 = String             #23            //  hello world
   #4 = Methodref          #24.#25        //  java/io/PrintStream.println:(Ljava/lang/String;)V
   #5 = Class              #26            //  com/fcs/jvms/NotInitialization
   #6 = Class              #27            //  java/lang/Object
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               LocalVariableTable
  #12 = Utf8               this
  #13 = Utf8               Lcom/fcs/jvms/NotInitialization;
  #14 = Utf8               main
  #15 = Utf8               ([Ljava/lang/String;)V
  #16 = Utf8               args
  #17 = Utf8               [Ljava/lang/String;
  #18 = Utf8               SourceFile
  #19 = Utf8               NotInitialization.java
  #20 = NameAndType        #7:#8          //  "<init>":()V
  #21 = Class              #28            //  java/lang/System
  #22 = NameAndType        #29:#30        //  out:Ljava/io/PrintStream;
  #23 = Utf8               hello world
  #24 = Class              #31            //  java/io/PrintStream
  #25 = NameAndType        #32:#33        //  println:(Ljava/lang/String;)V
  #26 = Utf8               com/fcs/jvms/NotInitialization
  #27 = Utf8               java/lang/Object
  #28 = Utf8               java/lang/System
  #29 = Utf8               out
  #30 = Utf8               Ljava/io/PrintStream;
  #31 = Utf8               java/io/PrintStream
  #32 = Utf8               println
  #33 = Utf8               (Ljava/lang/String;)V
  ...

发现常量在编译阶段会存入调用类的常量池中,本质上并没有直接引用定义常量的类,因此不会触发定义常量的类的初始化。

posted @ 2017-05-02 09:35  Lucare  阅读(156)  评论(0编辑  收藏  举报