在测试类的加载过程中写了一个测试代码验证类的初始化情况,当调用类的静态方法时,即当使用了字节码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编译器也有使用内联优化,有兴趣的请自行查阅。