还记得才接触到类的初始化时的窘态,如今再次学习jvm,理解这些底层的东西,希望可以得到更多的收获。首先看下面的代码:
绝大多数人应该都能想到程序运行的结果吧,但是为什么是如此的呢?
我们知道所有java虚拟机实现必须在每个类或接口被java程序“首次主动使用”时才初始化。
显然上面的案例中并没有触发Parent1类的初始化,原因如下:
在Parent1中我们定义的是编译期的常量,即这些常量在编译阶段会被存入调用这个常量的方法所在的类的常量池中,本质上:调用类并没有直接引用低昂一常量的类,因此不会触发定义常量的类的初始化。
如果将上面的常量去掉 final结果如何呢?
此时的str为静态变量,当我们访问某个类的静态变量时,属于类的主动使用,显然会触发类的初始化。
那如果是这样的代码呢?
此时,str属于运行期的常量,即当一个常量值并非编译期间可以确定的,那么其值就不会被放到调用类的常量池中,这时在程序运行时会导致主动使用这个常量所在的类,显然会导致类被初始化。那么其结果就一目了然了吧!
通过JVM参数TraceClassLoading (-XX:+TraceClassLoading),用于追踪类的加载信息,并打印出来,来查看类的加载情况。
JVM参数
开头:-XX:,这个是固定的
后面:+<option>,后面的+、-,代表开启、关闭option选项
例:
-XX:+<option>参数,代表开启option选项
-XX:-<option>参数,代表关闭option选项
还有一种是设置值的方式:
-XX:<option>=<value>,将option选项的值设置为value。
我们也可以借助idea进行反编译查看
javap 反编译命令
javap -c xx.class文件 显示反编译的概要内容
助记符:
ldc:表示将int、float或是String类型的常量值从常量池推送至栈顶
bipush:表示将单字节(-128~127)的推送至栈顶
sipush:表示将一个短整型的常量值(-32768~32767)的推送至栈顶
iconst_1:表示将int类型推送至栈顶(iconst_1~iconst_5,最多到5的常量,6以上就是bipush)
-1 是iconst_m1 0对应iconst_0
anewarray:表示创建一个引用类型的(如类、接口、数组)数组,并将其引用值压入栈顶
newarray:表示创建一个指定的原始类型的数组(如int,float,char等),并将其引用值压入栈顶
补充:
类的主动使用:
1. 创建类的实例
2.访问某个类或接口的静态变量或者对该静态变量赋值
3. 调用类的静态方法
4. 反射
5. 初始化一个类的子类
6. Java虚拟机启动时被表明为启动类的类