深入理解JVM(二):类的初始化

**

github地址:

https://github.com/lishanglei/jvm_study.git

**
java程序对类的使用方式可以分为两种:

  • 主动使用
  • 被动使用

所有的java虚拟机实现必须在每个类或接口被java程序首次主动使用时才初始化它们

主动使用(七种):

  1. 创建类的实例
  2. 访问某个类或接口的静态变量,或者对该静态变量赋值
  3. 调用类的静态方法
  4. 反射(如Class.forName(“com.test.Test”))
  5. 初始化一个类,会先初始化这个类的父类
  6. java虚拟机启动时被表明为启动类的类
  7. 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的初始化,初始化时对静态变量的赋值是按照代码的顺序进行的.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值