一道有趣的类加载面试题

题目

运行如下代码的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变量,并不会触发类的初始化操作。
posted @ 2019-10-29 10:27  lonecloud  阅读(729)  评论(0编辑  收藏  举报
我的博客即将同步至 OSCHINA 社区,这是我的 OSCHINA ID:lonecloud,邀请大家一同入驻:https://www.oschina.net/sharing-plan/apply