天天看点

final static修饰的变量的小疑惑

作者:IT农民工1024

在测试类的加载过程中写了一个测试代码验证类的初始化情况,当调用类的静态方法时,即当使用了字节码invokestatic指令。这个时候类会初始化调用<clinit>方法,此时类肯定会进行加载。

这时肯定理所当然认为,如果调用final static的静态变量时,类也肯定会加载。

测试代码如下:

package simple02;

public class Demo01 {
    public final static int NUM = 10000 ;
    public final static String ABC = "1234567" ;
    static {
        System.out.println("demo 加载初始化");
    }
    public static void out(){
        System.out.println("demo out");

    }
}
           
package simple03;

import simple02.Demo01;

public class LoadingTest {

    public static void main(String[] args) {
        System.out.println("已经进入main方法");
        try {
            Thread.sleep(2000);
//            int  i = Demo01.NUM;
            System.out.println( Demo01.ABC);
            Thread.sleep(2000);
            Demo01.out();
        } catch (Exception  e) {
            e.printStackTrace();
        }
    }
}
           

当我打开-XX:+TraceClassLoading去观察,发现在sout前并没有加载该类,在休眠2秒后,Demo01才加载。正常逻辑如果有调用肯定会加载,即使不初始化也需要加载相关类。

[Loaded sun.util.locale.provider.LocaleResources$ResourceReference from D:\Program Files\Java\jdk1.8.0_181\jre\lib\rt.jar]
1234567
[Loaded simple02.Demo01 from file:/D:/Coding/Java/jvm/target/classes/]
demo 加载初始化
demo out           

面对这个结果测试了两种类型,String类型,int类型结果都一样,并没有加载对应都类。

打开字节码文件,查看对应的字节码发现是ldc #9 <1234567>,说明在字节码文件中Demo01.ABC是以常量存在。

0 getstatic #2 <java/lang/System.out : Ljava/io/PrintStream;>
 3 ldc #3 <已经进入main方法>
 5 invokevirtual #4 <java/io/PrintStream.println : (Ljava/lang/String;)V>
 8 ldc2_w #5 <2000>
11 invokestatic #7 <java/lang/Thread.sleep : (J)V>
14 getstatic #2 <java/lang/System.out : Ljava/io/PrintStream;>
17 ldc #9 <1234567>
19 invokevirtual #4 <java/io/PrintStream.println : (Ljava/lang/String;)V>
22 ldc2_w #5 <2000>
25 invokestatic #7 <java/lang/Thread.sleep : (J)V>
28 invokestatic #10 <simple02/Demo01.out : ()V>
31 goto 39 (+8)
34 astore_1
35 aload_1
36 invokevirtual #12 <java/lang/Exception.printStackTrace : ()V>
39 return           

通过反编译代码发现 System.out.println( Demo01.ABC); ==> System.out.println("1234567");

这里表明javac的时候编译器已经做了一些优化,而且是真的static final定义的常量进行优化。

////
    public static void main(String[] args) {
        System.out.println("已经进入main方法");

        try {
            Thread.sleep(2000L);
            System.out.println("1234567"); //自动变成了字面量值
            Demo01.out();
        } catch (Exception var2) {
            var2.printStackTrace();
        }

    }
/////
           

查了很多资料,发现这其实是编译的优化,称为内联优化,简单通俗来讲就是把需要调用外部逻辑的步骤,嵌入到自身的逻辑中去,变成自身的一部分,之后不再调用该方法,从而节省额外开支。

那就是,用final static 修饰的变量,在编译的时候,编译器会把值放到调用者的常量池中,调用时候直接拿常量池的值,而不是去调用定义常量类的常量。

JIT编译器也有使用内联优化,有兴趣的请自行查阅。