jvm之类加载(2)详解

常量的本质定义

  • 运行下面两段代码:
    • -MyTest2
package com.finedo.jvm.classloader;

//常量在编译阶段会存入到调用这个常量方法所在的类的常量池中,
//本质上,调用类并没有直接引用到定义常量的类,因此并不会触发
//定义常量的类的初始化
//注意:这里指的是将常量存放到了MyTest2的常量池中,之后MyTest2与MyParent2就没有任何关系了
//甚至,我们可以将MyParent2的class文件删除

/*
    助记符:
    ldc表示将int,float或者String类型的常量值从常量池中推送至栈顶
    bipush表示将单字节(-128 - 127)的常量值推送至栈顶
    sipush表示将一个短整型常量值(-32768 - 32767) 推送至栈顶
    iconst_1表示将int类型1推送至栈顶(iconst_1 - iconst_5)
 */

public class MyTest2 {
    public static void main(String[] args) {
        System.out.println(MyParent2.m);
    }
}

class MyParent2 {
    public static final String str = "Hello world";
    public static final short s = 127;
    public static final int i = 128;
    public static final int m = 128;
    static {
        System.out.println("MyParent2 static block");
    }
}

//运行结果:
128
  • MyTest3
package com.finedo.jvm.classloader;

import java.util.UUID;

public class MyTest3 {
    public static void main(String[] args) {

        System.out.println(MyPatent3.str);
    }
}

class MyPatent3 {
    public static final String str = UUID.randomUUID().toString();

    static {
        System.out.println("MyParent3 static code");
    }
}

//运行结果:
MyParent3 static code
31708a66-fbca-40a4-9705-f4d232ce51d3


  • 运行上面两段代码,MyTest3静态代码代码执行了,而MyTest2静态代码块没执行,主要原因是:当一个常量的值并非编译期可以确定的,那么其值就不会被放到调用类的常量池中,这时程序运行时,会导致主动使用这个常量池所在的类,显然会导致这个类被初始化。

接口初始化规则

  • MyTest5
package com.finedo.jvm.classloader;
/*
    当一个接口在初始化时,并不要求其父类接口都完成了初始化
    只有在真正使用到父接口的时候(如引用接口中所定义的常量时),才会初始化
 */
import java.util.Random;
public class MyTest5 {
    public static void main(String[] args) {
        System.out.println(MyChild5.b);
    }
}

interface MyParent5 {
    public static int a = new Random().nextInt(3);
}
interface MyChild5 extends MyParent5 {
    public static int b = 5;
}

类加载器准备阶段和初始化阶段重要意义

  • 代码1
package com.finedo.jvm.classloader;
public class MyTest6 {
    public static void main(String[] args) {
        Singleton singleton = Singleton.getInstance();
        System.out.println("counter1:" + Singleton.counter1);
        System.out.println("counter2:" + Singleton.counter2);
    }
}
class Singleton {
    public static int counter1;
    public static int counter2 = 0;
    private static Singleton singleton = new Singleton();
    private Singleton() {
        counter1++;
        counter2++; 
    }
    public static Singleton getInstance() {
        return singleton;
    }
}
//运行结果
counter1:1
counter2:1
  • 代码2
package com.finedo.jvm.classloader;
public class MyTest6 {
    public static void main(String[] args) {
        Singleton singleton = Singleton.getInstance();
        System.out.println("counter1:" + Singleton.counter1);
        System.out.println("counter2:" + Singleton.counter2);
    }
}
class Singleton {
    public static int counter1;
    private static Singleton singleton = new Singleton();
    private Singleton() {
        counter1++;
        counter2++; //准备阶段的重要意义
    }
        public static int counter2 = 0;
    public static Singleton getInstance() {
        return singleton;
    }
}
//运行结果
counter1:1
counter2:0
  • 分析上面两段代码逻辑完全相同,只是定义的counter2 位置不同,其结果就完全不同,主要原因是:如代码2,当代码 Singleton singleton = Singleton.getInstance();执行时属于类的主动使用(调用类的静态方法),会初始化类,在初始化之前类加载会经过准备阶段,会为类的静态变量赋值,此时counter1=0,singleton =null,counter2 =0,而后初始化按从上到下执行,当执行到Singleton构造方法后counter1=1,counter2=1,当执行下一步后counter2又被赋值为0,故两次运行结果不同。

你可能感兴趣的:(java,jvm)