一道有趣的类加载面试题

  • 2019 年 10 月 29 日
  • 筆記

题目

运行如下代码的Test1与Test2分别输出什么结果

  public class Parent {      static {          System.out.println("Parent static invoke");      }      public static final String FINAL_STR="FINAL_STR";      public static final Test FINAL_OBJECT=new Test();      public Parent(){          System.out.println("Parent init");      }  }  public class Child extends Parent {      static {          System.out.println("Child static invoke");      }      public Child(){          System.out.println("child init");      }  }  public class Test {      public static void main(String[] args) {          System.out.println(Child.FINAL_STR);      }  }  public class Test2 {      public static void main(String[] args) {          System.out.println(Child.FINAL_OBJECT);      }  }  

结果:

运行Test1结果

FINAL_STR

运行Test2结果

Parent static invoke  cn.lonecloud.Test@5e2de80c

解析:

Test1结果解析:

  1. 由于在mian方法中打印语句调用的是Child.FINAL_STR变量。
  2. 从Child的类中可以得知,FINAL_STR为final并且为static变量,其在调用static final变量的时候不会触发类的初始化操作。所以结果如上

Test2结果解析:

  1. 由于Test2中引用的对象为父类Parent的静态变量,由于并不是常量池中的对象,所以,会触发Parent的初始化操作。

深究:

Test1字节码:

Compiled from "Test.java"  public class cn.lonecloud.Test {    public cn.lonecloud.Test();      Code:         0: aload_0         1: invokespecial #1                  // Method java/lang/Object."<init>":()V         4: return      public static void main(java.lang.String[]);      Code:         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;         3: ldc           #4                  // String FINAL_STR         5: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V         8: return  }  
  1. ldc #4 // String FINAL_STR其为获取静态化变量方法,其为将常量压入栈中,由于静态变量在JVM中存在常量池的概念,会将字符串进行优化,所以并不会触发类初始化

Test2字节码:

Compiled from "Test.java"  public class cn.lonecloud.Test {    public cn.lonecloud.Test();      Code:         0: aload_0         1: invokespecial #1                  // Method java/lang/Object."<init>":()V         4: return      public static void main(java.lang.String[]);      Code:         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;         3: getstatic     #3                  // Field cn/lonecloud/Child.FINAL_OBJECT:Lcn/lonecloud/Test;         6: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/Object;)V         9: return  }

由于需要使用到Child类的父类中的FINAL_OBJECT变量,未使用到Child类中的变量,所以不会对Child类进行初始化,初始化的为其父类。

查看JVM加载情况

通过添加JVM参数-XX:+TraceClassLoading可以查看类加载情况

可见,会对Child,Parent类进行类加载操作,但是调用static方法,只有Parent类会调用static进行初始化操作。

总结

  1. 如果引用了常量池变量(String,以及基本类型相关变量),如果该变量为final static进行修饰的时候,则不会对类进行初始化操作
  2. 如果为非常量池变量,如果调用方存在父子类关系,则实际JVM会加载子类与父类,但是如果使用的为父类的final变量,并不会触发类的初始化操作。