**
github地址:
https://github.com/lishanglei/jvm_study.git
**
java程序对类的使用方式可以分为两种:
- 主动使用
- 被动使用
所有的java虚拟机实现必须在每个类或接口被java程序首次主动使用时才初始化它们
主动使用(七种):
- 创建类的实例
- 访问某个类或接口的静态变量,或者对该静态变量赋值
- 调用类的静态方法
- 反射(如Class.forName(“com.test.Test”))
- 初始化一个类,会先初始化这个类的父类
- java虚拟机启动时被表明为启动类的类
- JDK1.7开始提供的动态语言支持:java.lang.invoke.MethodHandle实例的解析结果REF_getStatic,REF_putStatic,REF_invokeStatic对应的类没有初始化则初始化
除了以上七种情况,其它使用java类的方式都被看做是对类的被动使用,都不会导致类的初始化
public class MyTest1 {
public static void main(String[] args) {
System.out.println(MyChild1.str);
}
}
class MyParent1{
public static String str ="hello world";
static{
System.out.println("MyParent1 static block");
}
}
class MyChild1 extends MyParent1{
static {
System.out.println("MyChild1 static block");
}
}
运行结果:
MyParent1 static block
hello world
结果分析:
对于静态字段来说,只有直接定义了该字段的类才会被初始化.
首先,我们知道静态代码块中的代码是在初始化阶段被执行,而对于MyChil1来说,并没有被主动使用,所以MyChil1并不会进行初始化,对于MyParent1 ,调用了该类的静态字段就是对该类的主动使用,所以会使MyParent1 进行初始化.
public class MyTest1 {
public static void main(String[] args) {
//System.out.println(MyChild1.str);
System.out.println(MyChild1.str2);
}
}
class MyParent1{
public static String str ="hello world";
static{
System.out.println("MyParent1 static block");
}
}
class MyChild1 extends MyParent1{
public static String str2 ="welcome";
static {
System.out.println("MyChild1 static block");
}
}
输出结果:
MyParent1 static block
MyChild1 static block
welcome
结果分析:
显示调用MyChild1类的静态变量,是对MyChild1 的主动使用,会使jvm初始化MyChild1 类,根据主动使用的第五条,会先初始化它的父类MyParent1 .
* jvm指令格式:
* -XX:+<option> 表示开启option选项
* -XX:-<option> 表示关闭option选项
* -XX:<option>=<value> 表示将option选项的值设置为value
可以使用 -XX:+TraceClassLoading 命令将类的加载信息打印出来
public class MyTest2 {
public static void main(String[] args) {
System.out.println(MyParent2.str);
}
}
class MyParent2{
public static final String str ="hello world";
static {
System.out.println("MyParent2 static block");
}
}
输出结果:
如果str被final修饰,则输出
hello world
如果str没有被final修饰,则输出
MyParent2 static block
hello world
结果分析:
常量在编译阶段会存入到调用这个常量的方法所在类的常量池中,本质上,调用类并没有直接引用到定义常量的类,因此并不会触发定义常量的类的初始化.
注意:这里指的是将常量存放到了MyTest2的常量池中,之后MyTest2与MyParent2就没有任何关系了,甚至,我们可以将MyParent2的class文件删除
反编译MyTest2文件
可以看到 3: ldc #4 // String hello world 此处已经与MyParent2没有任何关系了
助记符:
ldc 表示将interesting,float,String类型的常量值从常量池推送至栈顶
bipush 表示将单字节(-128~127)的常量值推送至栈顶
sipush 表示将一个短整型常量值(-32768~32767)推送至栈顶
iconst_1 表示将int类型1推送至栈顶(iconst_m1 ~ iconst_5)
public class MyTest3 {
public static void main(String[] args) {
System.out.println(MyParent3.str);
}
}
class MyParent3{
public static final String str = UUID.randomUUID().toString();
static {
System.out.println("MyParent3 static block");
}
}
运行结果:
MyParent3 static block
0de9b88c-34fa-49f7-b003-7938671bbcea
结果分析:
str在编译期的值并不能被确定,会导致MyParent3 被初始化
public class MyTest5 {
public static void main(String[] args) {
System.out.println(MyChild5.b);
}
}
interface MyParent5 {
public static final int a = 5;
}
interface MyChild5 extends MyParent5{
int b =6;
}
删除编译后的MyParent5 和MyChild5的字节码文件,对运行都没有影响
- 接口中的字段,默认被public static final修饰
- 删除MyParent5的字节码文件,依然没有问题.表明:
- 当一个接口在初始化时,并不要求其父接口都完成了初始化.
- 删除了MyChild5的字节码文件,依然没有问题.表明:
- b的值在运行期已经确定,也会将b的值放置到MyTest5常量池中,此时与MyChild5不在有任何关系
- 只有在真正使用父接口的时候(如引用接口中所定义的常量时,才会初始化)
public class MyTest6 {
public static void main(String[] args) {
Singleton singleton =Singleton.getInstance();
System.out.println(singleton);
System.out.println(Singleton.counter1);
System.out.println(Singleton.counter2);
}
}
class Singleton{
public static int counter1;
public static int counter2=0;
private static Singleton singleton =new Singleton();
private Singleton(){
counter1++;
counter2++;
System.out.println("counter1: "+counter1);
System.out.println("counter2: "+counter2);
}
public static Singleton getInstance(){
return singleton;
}
}
输出结果:
counter1: 1
counter2: 1
1
1
public class MyTest6 {
public static void main(String[] args) {
Singleton singleton =Singleton.getInstance();
System.out.println(singleton);
System.out.println(Singleton.counter1);
System.out.println(Singleton.counter2);
}
}
class Singleton{
public static int counter1;
private static Singleton singleton =new Singleton();
private Singleton(){
counter1++;
counter2++;
System.out.println("counter1: "+counter1);
System.out.println("counter2: "+counter2);
}
public static int counter2=0;
public static Singleton getInstance(){
return singleton;
}
}
输出结果:
counter1: 1
counter2: 1
1
0
结果分析:
Singleton.getInstance();会触发Singleton的初始化,初始化时对静态变量的赋值是按照代码的顺序进行的.